DEV Community

Cover image for Retro Techno Generator: Your Pulse Plays 90s Acid Techno, Your Dancing Plays the Filters
xbill
xbill Subscriber

Posted on

Retro Techno Generator: Your Pulse Plays 90s Acid Techno, Your Dancing Plays the Filters

Presage SmartSpectra SDK Challenge Submission

This is a submission for the Presage SmartSpectra SDK Challenge

This article provides a step by step guide to Retro Techno Generator, a music toy built on the Presage SmartSpectra SDK for Node.js. A webcam reads your pulse, breathing and facial expressions, and a browser page turns them into 90s acid techno and hardcore with Web Audio. Every sound is synthesised; there are no samples.

https://github.com/xbill9/retro-techno-generator

Your breathing and pulse steer the music, your face plays the lead, and your dancing opens the filters. Pulse and breathing need you to hold still, and the music asks you to move, so the app makes the two take turns: dance and the movement drives the sound, freeze for a few seconds and it re-reads your pulse. A hard dance raises your pulse, and the next still moment pushes the tempo up.


What I Built

A Node server runs a SmartSpectra session on the local camera and streams the signals over a WebSocket to a page on localhost. The page is the instrument: a 16-step sequencer, a 303-style acid line, drum synthesis, pads, piano stabs and a hoover, all built from Web Audio oscillators and filters.

Signal What it plays
Pulse Tempo is pulse × 2, held between 100 and 190 BPM. Below about 70: ambient pads. 70 to 90: acid techno. Above about 90: early-90s hardcore breaks and piano stabs
Movement Opens the master filter, brightens the acid line and doubles the hi-hats
Breathing waveform Sways the master filter while you are still
Smile Opens the acid filter and switches to a major key
Surprise Fires a hoover synth
Blink Stutters the drums and tears the visuals
Talking Pitched-up formant "vocal" chops

Retro Techno Generator in acid mode: HUD with mode, BPM, pulse, breaths per minute, smile, movement and vitals state, an oscilloscope trace and the 16-step grid

The style switch has 3 BPM of hysteresis at each edge, so a pulse sitting at 70 does not flip between ambient and acid every bar. A dropdown overrides the pulse-picked style for anyone who wants to stay in hardcore.


Demo

The clip is 34 seconds of a live camera session, recorded with the page's own Record button, with the camera preview hidden. The status line reads "in position", the breathing rate follows the SDK from 14 down to 6 and back to 12 breaths per minute, VITALS switches to HELD while I move and back to LIVE when I freeze, and the mode dropdown steps through acid, hardcore and ambient. The pulse readout holds its starting value of 72 for the whole clip: no pulse reading arrived during that session, so the tempo stays on its default.


Code

https://github.com/xbill9/retro-techno-generator

MIT licence. Setup is in the README and below.

At this point you should have…

Step 1 — Install and Run

git clone https://github.com/xbill9/retro-techno-generator
cd retro-techno-generator
npm install
echo "SMARTSPECTRA_API_KEY=your-key" > .env
npm start
Enter fullscreen mode Exit fullscreen mode
Retro Techno Generator on http://localhost:8080 (live camera)
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:8080 and press Start. npm run fake runs the whole synth with sliders in place of the camera and needs no key, which is the quickest way to hear it.

Step 2 — Pick a Camera

node server.js --list-cameras
Enter fullscreen mode Exit fullscreen mode

Set CAMERA=<id> in .env to use a camera other than the default.


How It Responds to Human Signals

Step 3 — Open the SmartSpectra Session

The server creates the session when the page sends start, and requests breathing, cardio and face metrics in one session:

sdk = new S.SmartSpectraSDK({
  apiKey: KEY,
  requestedMetrics: [...S.breathingMetrics, ...S.cardioMetrics, ...S.faceMetrics],
  enableTelemetry: false,
});
sdk.on('metrics', (buf) => onMetrics(S.decodeMetrics(buf)));
sdk.on('validationStatus', (code, _ts, hint) => broadcast({ type: 'status', code, hint }));
sdk.on('videoOutput', onVideo);
sdk.useCamera(S.CameraSelection.default);
sdk.start();
Enter fullscreen mode Exit fullscreen mode

onMetrics takes the latest pulse rate, breathing rate and upper-chest breathing trace, the blink and talking flags, and the expression scores, and sends them to the page as one small JSON message. On Stop the server calls stopAsync() and then destroy(), which turns the camera off.

Step 4 — Show the Person Where to Stand

The SDK measures only when it can see your face and upper chest, lit from the front, and it reports what is wrong through validationStatus codes. With a face in view and the chest below the frame it repeats this every frame:

VALID 16 Move up, or tilt the camera down.
VALID 7 Place more of the chest in view.
Enter fullscreen mode Exit fullscreen mode

A status line is easy to miss when you cannot see yourself, so the page has a mirror view. The SDK's videoOutput event delivers each processed frame, 1280×720 RGB at 30 frames per second on this laptop's camera:

VO 2764800 1280 720 3840 0
video frames 149
Enter fullscreen mode Exit fullscreen mode

The server shrinks every third frame or so to 320×180 and sends about ten a second as a binary WebSocket message. The page draws it mirrored, with a dashed head-and-shoulders outline, a border that is red until you are in position, yellow while the first readings arrive and green once they flow, and one large instruction mapped from the SDK code:

Code SDK name On screen
5 kTooDark Too dark. Put a light in front of you
7 kChestNotVisible ⬇ Show your shoulders: lean back or tilt the screen forward
12 kExcessiveMotion Moving. Freeze for a few seconds to re-read pulse
16 kFaceTooLow ⬆ Move up, or tilt the screen forward

After the first good reading the preview shrinks to a corner thumbnail and stays there, so dancing does not bring the full panel back. A Pixelate button draws the preview as coarse blocks, enough to line up against the outline without showing a recognisable face, for streaming or recording. Camera view hides the preview entirely, which is how the demo was recorded.

Step 5 — Turn Movement Into an Input

kExcessiveMotion is the SDK saying what every camera vitals method needs: stillness. A dance app cannot ask for that all the time, so the same 320×180 frames give a movement level, the mean brightness change per pixel between consecutive previews:

for (let p = 0, q = 4; p < luma.length; p++, q += 3) {
  luma[p] = (out[q] * 77 + out[q + 1] * 150 + out[q + 2] * 29) >> 8;
  if (prevLuma) diff += Math.abs(luma[p] - prevLuma[p]);
}
broadcast({ type: 'motion', value: diff / luma.length });
Enter fullscreen mode Exit fullscreen mode

Camera noise sets a baseline that depends on the room light. Sitting still in a dim room, 100 samples measured:

{"n":100,"min":3.03,"p50":3.56,"p90":8.58,"max":17.58}
Enter fullscreen mode Exit fullscreen mode

The page tracks that baseline from below and subtracts it, so sitting still reads 0.00 and moving reads up to about 0.37 on a 0 to 1 scale. Movement then drives the master filter, the acid filter and the hi-hat density.

Step 6 — Read Vitals Only When Still

Pulse and breathing updates are accepted only while the movement level stays under 0.15 and has stayed there for 1.5 seconds. Otherwise the last good values hold. The HUD shows the state as VITALS LIVE or VITALS HELD. Sampled every 500 ms in the page:

["0.00","LIVE",72],["0.06","LIVE",72],["0.26","HELD",72],["0.14","HELD",72],
["0.37","HELD",72],["0.17","HELD",72],["0.04","HELD",72],["0.02","LIVE",72]
Enter fullscreen mode Exit fullscreen mode

The tempo closes 5% of the gap to the held pulse on every sixteenth-note step, so a fresh reading after a dance ramps the track up over a few bars.

🔎 Tip: light from in front matters more than anything else. With a window behind you the camera exposes for the window, and the SDK reports Too dark or finds no face at all. A desk lamp aimed at your face fixes both.


Prize Categories

Most Creative or Delightful Use. The idea came from the rave flyers of the early 90s promising music that moves with you. Here the music listens to the body directly: your pulse picks between ambient, acid and hardcore, a smile flips the key to major, a surprised face fires a hoover, and a blink stutters the drums. The part that makes it play like an instrument is the turn-taking between dancing and stillness. Dance hard, freeze, and you hear your own heart rate push the track into hardcore.


Consent and Data Handling

  • Consent first. The page opens on a consent screen that says what the camera is for. The camera turns on only when you press Start and turns off when you press Stop.
  • Video stays on the machine. Frames go from the camera to the SDK in the local Node process. A 320×180 copy goes to the page for the mirror view. No frame is recorded or written to disk. Pixelate reduces that view to coarse blocks for anyone streaming or recording their screen. The app writes no camera frame to disk. The one exception is the ● Record button, used for the demo: only while it is on, it saves a .webm of the page as shown, with the synth audio, to the browser's Downloads folder.
  • Local only. The server listens on 127.0.0.1, so nothing outside the machine can open the page or the WebSocket.
  • Nothing is stored. No measurements, frames or logs of signals are kept. Closing the page or pressing Stop ends everything.
  • SDK telemetry is off (enableTelemetry: false). Per Presage's Telemetry & Privacy page, the SDK then starts no telemetry session and transmits no session summary, and even when on, that summary never contains video frames or measured values.
  • Network. The SDK needs a connection: measurements run inside an authenticated session with Presage's servers, opened with your API key.
  • No AI services are used by the app.
  • A music toy. It makes no health or medical claims and takes no decisions from the numbers. It turns them into music.

Summary

The goal of this article was to build a music toy that responds to the body in real time with the Presage SmartSpectra SDK. The key to the solution was letting dancing and stillness take turns: movement drives the sound, and pulse and breathing are read only in the still moments between. The results were:

  • 🟢 Breathing, pulse, smile, surprise, blink and talking each drive a part of the music
  • 🟢 In the demo the breathing rate updates live from the SDK, from 14 to 6 and back to 12 breaths per minute
  • ❌ No pulse reading arrived during the recorded session, so the demo's tempo stays on its default
  • 🟢 The SDK's videoOutput frames feed a mirror view with plain instructions mapped from the SDK's validation codes
  • 🟢 The same frames give a movement level that reads 0.00 sitting still and drives the filters while you dance
  • 🟢 Pulse and breathing update only while still, and the HUD shows LIVE or HELD
  • ⚠️ The SDK needs your face and upper chest in frame and light in front of you before it sends any metrics
  • ⚠️ The movement baseline was tuned in one dim room; a much noisier camera may need a different scale

Scope: one Linux laptop with its integrated 1280×720 camera, SmartSpectra Node SDK 3.4.0, Chrome, measured on 2026-11-06. The movement figures come from one 100-sample still run and one 17-second mixed run in a dim room, and the demo is one 34-second session. macOS and Windows were not tested.

The strategy for turning real-time human signals into music with the SmartSpectra SDK was validated with an incremental step by step approach.

This app and article were created with the help of AI: the code, the write-up and the demo recording tool were built with Claude Code, and every figure was checked against the runs and files linked in the repo.


References

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.