Skip to content

Releases: CaYatur/SoundVisualizer

3.1.4 — Streaming overlay and transparency fixes, and MilkDrop fidelity

Choose a tag to compare

@CaYatur CaYatur released this 14 Sep 21:33

⬇️ Download

Get this one Or You should know
🪟 Windows ⬇️ Installer Portable
no install
Only the installer registers the Dynamic Lighting background identity. Per-application audio capture needs Windows build 20348 or newer.
🍎 macOS ⬇️ .dmg .zip Apple Silicon. Unsigned — one Terminal command is needed, see below.
🐧 Linux ⬇️ AppImage .deb
Debian / Ubuntu
x64. Needs PulseAudio or PipeWire, which desktop installations already have.

Important

🍎 macOS: the app will say it is damaged. It is not.

This build is not signed — the Apple Developer Program has not been bought. macOS puts a quarantine flag on anything unsigned that arrives from the internet and refuses to open it, reporting it as "damaged and can't be opened." Right-clicking and choosing Open does not clear this on macOS 15 and later.

Open the .dmg, drag the app into Applications, then run this once in Terminal:

xattr -dr com.apple.quarantine "/Applications/CAYADEV Visualizer.app"

After that it opens normally, every time. The command removes the download flag; it does not change the application.

Then, for sound: macOS has no way to record its own output, so install BlackHole (free) and pick it as the audio source. Microphones and line inputs work with no extra setup — macOS will ask for microphone permission the first time.


An interim release. v3.1.3's streaming overlay was blank for everyone who used it, and the transparent background had never actually worked. Both are fixed here, together with the MilkDrop fidelity work that had been waiting on main since v3.1.3. The refinements this work turned up are planned for v3.1.5 rather than rushed in.

On v3.1.3 and using Streaming Output (OBS or a browser)? Update to this release — the overlay page cannot start in v3.1.3.

Streaming overlay works again (#563)

In v3.1.3 the overlay page crashed on every load. Aspect correction made the visualizer call SVAspect while sizing its canvas, and the overlay page never loaded aspect.js — nor timeline.js, clipdeck.js or three MilkDrop modules. The page stopped at a 2×2-pixel canvas.

It looked like a connection problem, and it was not one. Probed as an OBS client, the v3.1.3 stream server sent its greeting, the configuration, the presets and audio frames within 40 ms of connecting; the page had already failed. The reason was invisible because the start-up error went into a box the overlay hides, and nothing reached the console.

  • The overlay loads everything the desktop visualizer loads, and a unit test compares the two script lists so they cannot drift apart again.
  • A start-up failure is no longer a silent blank page. The error is shown on the page and logged to the console.
  • The self-test now opens the overlay from the real stream server on every run and fails unless the page connects and starts. The packaged build runs the same self-test, so the page is also checked as served from inside the archive. Checked by deleting the aspect.js tag and watching the self-test fail with v3.1.3's exact error.
  • The overlay follows the app's Transparent Background switch. It no longer rewrites the background type to transparent, which dropped background effects and sent text through brightness-as-alpha.

Transparent background — for real

Background → Transparent Background creates a transparent window, but the page then painted its own background colour inline on top of it — an inline style beats the rule that was meant to clear it — so the desktop never showed through, in any version. Every settings change wrote the colour again.

Separately, every post-processing pass wrote an opaque alpha. With a single effect enabled, the transparent streaming overlay was an opaque rectangle in OBS, and so was the transparent window.

  • The page stays clear in transparent mode, and alpha survives post-processing: in transparent mode the scene goes through the effect chain premultiplied, and coverage is taken from the source alpha so dark glyphs, black outlines and shadows stay intact. Bloom and blur can still spread into empty pixels. Projection mapping gets the same treatment. Nothing changes in the opaque path.
  • Background effects can be transparent too. A background effect paints every pixel, so it used to hide the desktop completely. Its dark parts now turn transparent below a Transparency Threshold — black always, bright areas stay — applied the same way whether or not post-processing is on.
  • Windows no longer uses exclusive fullscreen when the window is transparent, so the desktop shows through instead of a black frame. Open visualizer windows are recreated when the switch changes.
  • The layer stack exposes the same switch at the top of the layer list (the Background card is hidden while the stack is on).
  • Spout/Syphon stays opaque. The GPU shared texture cannot carry alpha without dropping the sender, so that output paints the full scene. Use the local window or the OBS browser source for a transparent overlay.
  • A blackout stays black, as it already did in the overlay.

Measured with a screen capture of a second display (liquid background, desktop average rgb(39, 39, 39)): rgb(51, 137, 129) at threshold 0, rgb(51, 94, 85) at threshold 1, and rgb(52, 99, 90) at threshold 1 with bloom enabled. The window stayed transparent with Always on top switched on. The self-test reads the pixels of a white square drawn on a clear canvas through bloom: the empty corner is transparent and the square opaque.

A regular browser tab always draws a page over its own background, so it cannot show what is behind it — transparency is visible in OBS's Browser Source. Add ?transparent=0 to the overlay address to force an opaque source, or ?transparent=1 to force a keyed one.

MilkDrop, much closer to MilkDrop (#560)

Everything below was measured against a 10,332-preset corpus and checked against Nullsoft's own MilkDrop 2 source.

  • Every shader stage in the corpus compiles. The first measurement after v3.1.3 found 94.3%; the causes were traced one by one, including HLSL's implicit conversions, array initialisers and bool used as a number.
  • New engine features: textured shapes, motion vectors, rotation matrices, mesh density, internal resolution scale and mouse input; preset transitions as MilkDrop's dual pipeline (both presets keep running while their warp meshes blend per node); and user textures loaded from a MilkDrop texture folder (16.9% of presets ask for one).
  • Fidelity fixes from the source: the mesh transform's order, the swapped aspectx/aspecty, a vertically mirrored warp mesh, the warp ripple's real constants, header values that never reached the equations, the shader clock, per-frame variables and t1..t8 reloading every frame, the blur chain reading the warp's input, vertex colours wrapping instead of clamping, borders and centre darkening, custom waves at their real size and source, MilkDrop's waveform smoothing, and a feedback buffer that clamps like MilkDrop's without losing precision.
  • Presets hear the music the way MilkDrop hears it. bass, mid, treb and their _att values now come from MilkDrop's own analysis chain. The old path put bass lower just after a kick than between kicks, and _att never rose on a hit; MilkDrop's chain gives 2.45× on a kick. Spectrum waves read the newest audio instead of sound from ~30 ms earlier.
  • Waveforms, outlines and motion vectors are drawn as anti-aliased ribbons by default, with MilkDrop's own line drawing as one setting for all three; shape outlines honour thick.
  • Safer to watch: flash limiting caps sudden full-screen luminance changes at WCAG's general-flash value; 90% of presets never reach it and are left untouched.
  • Fixed: auto advance now advances (the setting had never been read); a MilkDrop layer survives scene transitions instead of restarting its preset and losing its trail — which happened on every track with the dynamic colour theme on; a MilkDrop scene exported as a 0-byte video; MilkDrop did not render in the streaming overlay.
  • The preset's own monitor variable is shown live in the MilkDrop panel, and a decay of exactly zero is honoured.

881 of 900 sampled presets render a live image, measured frame by frame in a real WebGL context.

Also in this release

  • The self-test puts your settings back when it exits; it runs against the real profile and could leave settings.json as factory defaults plus test values.
  • The release translation gate no longer fails when a Turkish song title is playing.

Verification

1597 unit tests pass, 469 of them added since v3.1.3. The GPU smoke test passes, and the packaged Windows build passes its own self-test — including the new streaming overlay check, the transparency pixel check and a video export of a MilkDrop scene (1,255 KB, no black frames).

3.1.3 — Per-application audio capture, aspect correction, and a rebuilt Auto VJ

Choose a tag to compare

@CaYatur CaYatur released this 08 Sep 03:31

⬇️ Download

Get this one Or You should know
🪟 Windows ⬇️ Installer Portable
no install
Only the installer registers the Dynamic Lighting background identity. Per-application audio capture needs Windows build 20348 or newer.
🍎 macOS ⬇️ .dmg .zip Apple Silicon. Unsigned — one Terminal command is needed, see below.
🐧 Linux ⬇️ AppImage .deb
Debian / Ubuntu
x64. Needs PulseAudio or PipeWire, which desktop installations already have.

Important

🍎 macOS: the app will say it is damaged. It is not.

This build is not signed — the Apple Developer Program has not been bought. macOS puts a quarantine flag on anything unsigned that arrives from the internet and refuses to open it, reporting it as "damaged and can't be opened." Right-clicking and choosing Open does not clear this on macOS 15 and later.

Open the .dmg, drag the app into Applications, then run this once in Terminal:

xattr -dr com.apple.quarantine "/Applications/CAYADEV Visualizer.app"

After that it opens normally, every time. The command removes the download flag; it does not change the application.

Then, for sound: macOS has no way to record its own output, so install BlackHole (free) and pick it as the audio source. Microphones and line inputs work with no extra setup — macOS will ask for microphone permission the first time.


Capture the audio of one application instead of the whole system, correct displays whose pixels are not square, and a rebuilt Auto VJ that says what it is doing.

Per-application audio capture — Windows

Until now the only option was the full system mix, so a notification sound or a chat voice moved the visuals. An application is now a source in its own right.

  • WASAPI process loopback through a self-contained .NET helper (native/app-audio-helper), because ActivateAudioInterfaceAsync with VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK is COM-only and cannot be reached from Node. Chosen over a native node addon so it does not have to be rebuilt against every Electron ABI bump. Requires Windows build 20348 or newer.
  • A source kind, not a mode. An application target sits in the same source list as devices and mixes into the same buffer before the FFT, so "Spotify + microphone" is one selection rather than a special case. Several applications can be captured at once.
  • Include or exclude. Capture only the chosen applications, or everything except one. Exclude is limited to a single application: the OS interface takes one process id, and two exclusion streams would each carry the other application's audio and double-count it when mixed.
  • Targets are stored by executable name, not process id. A pid changes every time the application restarts; a source bound to one would silently fall to silence. The saved target is re-resolved on every start, preferring a process that is actually producing audio.
  • Re-attaches on its own. Choosing an application that is not running yet is normal — the capture stays alive, silent, and attaches when the application appears. It also survives that application being restarted. It does not fall back to the system mix: closing an application is usually deliberate, and falling back would put the sounds you excluded back on the screen.

Measured against the packaged build: a browser playing music gave 299 packets / 143,520 frames in 3 s at 48 kHz, peak amplitude 0.21. End to end through the analysis pipeline, one application gave a peak bin of 236/255; two applications mixed with the default output device gave 255. Exclude was checked twice rather than once, because a single measurement cannot tell filtering apart from a dead stream: excluding the browser that was playing gave peak 0, while excluding a different, silent application gave peak 247.

macOS and Linux: the platform rules are implemented and tested, and both report clearly why they cannot capture, but neither has a capture backend yet. macOS needs ScreenCaptureKit on 13+, Linux PipeWire or PulseAudio.

Aspect correction — all platforms

A display's reported resolution does not always match its physical shape. A panel driven at 1920x1080 that is really about 3:1 — a stage LED wall, a bar display, an anamorphic projector, a TV forced into a stretched mode — draws every circle as an ellipse. Logos come out squashed, and so does everything else: text, effects, the background.

  • The frame is never stretched. Scaling a finished frame up crops it and scaling it down leaves bars. Instead the scene is drawn on a square-pixel canvas matching the panel's real shape and squeezed linearly into the framebuffer, where the panel's own distortion undoes the squeeze. Nothing is cropped and nothing is pushed off the edge.
  • One setting corrects everything — background, visualizer, logo, text and sprites share the same logical space, so pre-stretching each image in another program stops being necessary. If you already did that, swap those files back to their originals: they carry the compensation already and would otherwise be corrected twice.
  • Calibrated by eye. Nobody can measure the LED wall behind the stage, but anyone can see whether a circle is round: turn on a circle, square or grid and move the slider until it looks right. Panel size, true aspect ratio, and the dimensions of an image you already stretched by hand are secondary paths to the same number.
  • Full resolution by default. Splitting the difference between the axes keeps the pixel count identical but, at a real pixel ratio of 1.68, renders 833 rows and stretches them over the panel's 1080 — throwing away resolution the display can show. The default keeps every axis at or above the framebuffer; the cost is stated in the panel and capped so an extreme ratio on a 4K panel cannot stall the render.
  • Per screen or all screens, and it composes with projection mapping — mapping coordinates are normalized and this squeeze is linear, so an existing corner calibration does not move.
  • A property of the physical output, not of the scene: exported video, the stream and the web overlay are left alone, because they are watched on other screens where the correction would itself be the distortion.

Measured: on a 1920x1080 window with a pixel ratio of 1.681 the logical canvas becomes 3228x1080, and a shape drawn 1:1 comes out at 0.9999 — round. With the correction off the same measurement reports 1.68, so it distinguishes the two cases rather than always agreeing.

Tempo and Auto VJ, rebuilt

Auto VJ was reported as not working. It had been working the whole time; it had no way to say so, and four other faults were silent.

  • The silent failure. The default source was Scenes, and with no saved scenes — every new install — the switch failed and returned false. Nothing changed, nothing was printed, and the feature was indistinguishable from a broken one. The default is now Visualizers, which is never empty, and a status line reports what changed, what is next, and why nothing can happen when a source is empty.
  • Clicks were being dropped. The panel rebuilt itself on every switch, so a click landing during the rebuild hit a node that was being replaced. This is why the source buttons appeared dead. The status line now updates in place and the panel is not rebuilt while running.
  • Text layers were destroyed. A text layer carries kind: 'visualizer' with type: 'text'. Auto VJ took the first match and overwrote its type, turning a configured text layer into a spectrum analyzer with no undo.
  • Two visualizers collapsed into one. Every targeted layer was given the same type. Each layer now draws its own, distinct within a switch.
  • Colour presets did nothing visible unless the background happened to be in gradient mode; they now drive the visualizer colours as well.
  • Scenes bled into each other, and Auto VJ's private copy of the scene loader was missing blackout protection — with the output blacked out, a scene switch would have lifted the blackout on stage. There is now one loader.
  • More to cycle, and control over it. Each source takes an optional list of exactly which scenes, visualizers or presets to cycle, empty meaning all; colour presets can be limited to built-in or user-made; each visualizer layer can get its own mode. The rotation now covers all 46 visualizer modes the type picker offers rather than the 30 that were hard-coded — spectrogram, flowfield, galaxy, dna and milkdrop among those it had never once reached.

Upgrading? The default source changed for new installs only. If your settings already say Scenes and you have no saved scenes, Auto VJ will still do nothing — but it now tells you why. Change the source or save a scene.

Preview, layers and Now Playing (#561)

An architectural review of v3.1.2 found the admin preview and the visualizer window drifting apart, and several layer and overlay faults behind it. All of it is fixed here.

  • The preview now shows what the visualizer shows. It was rendering the raw static config: timeline autom...
Read more

3.1.2 — MilkDrop shader engine, Now Playing overlay, and transparent visualizer

Choose a tag to compare

@CaYatur CaYatur released this 05 Sep 00:05

⬇️ Download

Get this one Or You should know
🪟 Windows ⬇️ Installer Portable
no install
Only the installer registers the Dynamic Lighting background identity.
🍎 macOS ⬇️ .dmg .zip Apple Silicon. Unsigned — one Terminal command is needed, see below.
🐧 Linux ⬇️ AppImage .deb
Debian / Ubuntu
x64. Needs PulseAudio or PipeWire, which desktop installations already have.

Important

🍎 macOS: the app will say it is damaged. It is not.

This build is not signed — the Apple Developer Program has not been bought. macOS puts a quarantine flag on anything unsigned that arrives from the internet and refuses to open it, reporting it as "damaged and can't be opened." Right-clicking and choosing Open does not clear this on macOS 15 and later.

Open the .dmg, drag the app into Applications, then run this once in Terminal:

xattr -dr com.apple.quarantine "/Applications/CAYADEV Visualizer.app"

After that it opens normally, every time. The command removes the download flag; it does not change the application.

Then, for sound: macOS has no way to record its own output, so install BlackHole (free) and pick it as the audio source. Microphones and line inputs work with no extra setup — macOS will ask for microphone permission the first time.


A complete MilkDrop shader and equation rewrite that brings presets back to life in packaged builds, live Now Playing overlays via Windows Media Transport Controls (SMTC), and a transparent visualizer window mode.

MilkDrop Shader Engine (#559)

MilkDrop presets had never executed in packaged application builds because the equation compiler generated JavaScript strings evaluated via new Function(), which Electron's Content Security Policy deliberately and silently blocked.

This release completely rewrites the equation compiler and adds a full HLSL-to-GLSL pixel shader translation pipeline for MilkDrop 2 warp and composite shaders:

  • Closure-tree compiler: Instead of evaluating raw JavaScript strings, the equation engine compiles MilkDrop equations into an AST closure tree. This eliminates the CSP violation without compromising security (unsafe-eval is never used) and provides approximately a 51× execution speedup.
  • HLSL to WebGL2 GLSL translation: MilkDrop 2 pixel shaders are translated with multi-pass type inference, function signature resolution, and operator lowering. Across all 10,347 presets in the official projectM and community packs, 91.7% of all shader stages compile, and 85.0% of presets run with completely clean shader pipelines.
  • Warp repeat wrapping: MilkDrop defaults to REPEAT texture wrapping on warp sampling, whereas earlier builds clamped edges to border. Content drifting off the edge was lost, causing presets to collapse and empty (e.g. Agitator brightness collapsing from 16.7 to 0.7). Wrapping is now active, keeping energy circulating in the feedback loop.
  • Feedback buffer saturation clamping: Moving feedback buffers to floating point removed MilkDrop's hardware 8-bit saturation clamp, causing presets that multiply feedback values (e.g. ret *= 5) to blow out to pure yellow or white. Explicit saturation clamping restores original color dynamics.
  • Eight waveform modes and custom shapes/waves: Complete implementation of all eight MilkDrop waveform geometries (nWaveMode 0–7: circular, rotating blob, dual Lissajous, smoothed ribbons, rotating figure-eight, angled dual lines) and custom shape/wave equation passes, aligned with the BeatDrop / MilkDrop reference source.
  • Audio scaling: Presets expect bass, mid, and treb as ratios relative to long-term averages (1.0 = normal). The engine now feeds normalized spectral ratios rather than raw 0..1 amplitudes, allowing subtle and explosive preset dynamics to react as their authors intended.
  • What is deferred to Phase 2 (quality pass): Sprite/user texture binding (img.ini currently connects to procedural noise), motion vectors, preset crossfade blending, and mouse interaction.

Now Playing Overlay

A dedicated overlay mode that reads track metadata and playback position from the operating system's media transport controls (Windows SMTC) and renders them onto the canvas:

  • Windows SMTC integration: Operates through an event-driven native C# WinRT helper (smtc-helper.exe, modeled after Windows-to-Android-Bridge) connecting directly to OS delegates (CurrentSessionChanged, MediaPropertiesChanged, PlaybackInfoChanged, TimelinePropertiesChanged) at 0.0% idle CPU and ~3 MB RAM, with transparent automatic fallback to PowerShell 5.1 if absent.
  • Anchor-based time extrapolation: Windows SMTC does not advance playback position continuously — samples often remain identical across several seconds before jumping forward. Every sample is therefore treated as a time anchor ({ position, updated }), and the current playback head is computed locally via closed-form interpolation without requiring per-frame IPC traffic.
  • Visual styling & animations: Includes 7 entrance animations (fade, slideUp, slideLeft, scale, typewriter, wipe), marquee scrolling for long titles exceeding display boundaries, audio-reactive bass pulse, and selectable Modern or Classic OG Winamp styles (with continuous or 40-segment progress bars).
  • Text mode binding: The standard Text layer mode can also connect its 'now' source directly to live SMTC data, falling back to manual text when no media player is active.

Transparent Visualizer Window (Addendum 4.0)

A new background.transparent toggle enables full visualizer window and canvas transparency:

  • The visualizer window is instantiated with transparent: true and #00000000 background color.
  • Canvas layers execute clearRect instead of solid color fills, and body CSS styling is made transparent.
  • Enables placing visualizations, frequency bars, and Now Playing widgets directly on top of the desktop, game feeds, or transparent capture windows.

What is verified, and what is not

  • 971 unit tests, all passing on main (up from 808 in v3.1.1 and 703 in v3.1.0). 42 new unit tests verify tracker grace periods, anchor extrapolation, envelopes, formatting, and native SMTC helper integration.
  • GPU self-test (npm run smoke) verifies all 50 visualizer modes, 31 backgrounds, 40 post-FX shaders, window narrowing, accidental-close protection, and confirms zero untranslated strings across all admin categories in English.
  • Windows packages built locally with Dynamic Lighting certificate registration. macOS Apple Silicon (.dmg, .zip) and Linux x64 (.AppImage, .deb) packages built and structurally verified on GitHub Actions CI runners.

3.1.1 — macOS and Linux builds, OpenRGB, Spout and Syphon

Choose a tag to compare

@CaYatur CaYatur released this 03 Sep 21:30

⬇️ Download

Get this one Or You should know
🪟  Windows ⬇️  Installer Portable
no install
Only the installer registers the Dynamic Lighting background identity.
🍎  macOS ⬇️  .dmg .zip Apple Silicon. Unsigned — one Terminal command is needed, see below.
🐧  Linux ⬇️  AppImage .deb
Debian / Ubuntu
x64. Needs PulseAudio or PipeWire, which desktop installations already have.

Important

🍎 macOS: the app will say it is damaged. It is not.

This build is not signed — the Apple Developer Program has not been bought. macOS puts a quarantine flag on anything unsigned that arrives from the internet and refuses to open it, reporting it as "damaged and can't be opened." Right-clicking and choosing Open does not clear this on macOS 15 and later.

Open the .dmg, drag the app into Applications, then run this once in Terminal:

xattr -dr com.apple.quarantine "/Applications/CAYADEV Visualizer.app"

After that it opens normally, every time. The command removes the download flag; it does not change the application.

Then, for sound: macOS has no way to record its own output, so install BlackHole (free) and pick it as the audio source. Microphones and line inputs work with no extra setup — macOS will ask for microphone permission the first time.


The application leaves Windows. macOS and Linux packages, RGB lighting on all three platforms through OpenRGB, and the output handed to other applications over the GPU with Spout and Syphon.

macOS and Linux

Both are built by GitHub Actions on their own runners, because audify is a native module and cannot be cross-compiled — a macOS package produced on Windows shows the interface and captures no audio.

  • macOS.dmg and .zip, Apple Silicon only. Intel is not in this release: Apple has finished the transition and GitHub's Intel runners queue for hours.
  • Linux.AppImage and .deb, x64. The .deb declares libpulse0 among its dependencies, which is what the audio engine actually links against.

Windows is still packaged locally rather than in CI. The installer registers the Dynamic Lighting background identity from a certificate it generates itself, and the runner's PowerShell cannot open the Cert: drive — a CI-built installer would lack that identity, which makes it a different product.

System audio is not the same problem on all three. Windows opens an output device with WASAPI loopback. Linux has to open the PulseAudio or PipeWire monitor, which the system reports as an input — so the application recognises monitors, marks them as loopback and prefers them by default. macOS has no loopback at all and needs a virtual device such as BlackHole; there is no way around that, and the panel says so rather than presenting an empty list.

No Node.js next to the application

The audio helper is a separate process, and until now the Windows package carried a 93 MB node.exe so users would not have to install one. The other two platforms would have got nothing.

Electron is itself a Node runtime. Running the app's own binary with ELECTRON_RUN_AS_NODE=1 starts the helper, and audify loads under it because it is N-API. That removes the bundled node.exe from Windows and gives macOS and Linux a runtime that is guaranteed to be there. A system Node install is still tried as a fallback, and when neither works the error names which runtime was missing instead of saying the helper failed.

OpenRGB

Dynamic Lighting is a Windows service, so on macOS and Linux that card is replaced by a note saying as much. OpenRGB answers on those platforms — and is an additional option on Windows, not a replacement.

  • Speaks the OpenRGB SDK protocol directly over TCP (port 6742 by default). No vendor software, no driver, and the server may sit on another machine.
  • Drives every device the server exposes, per-LED where the hardware allows it.
  • It shares one renderer with Windows Dynamic Lighting, so all ten dynamic modes and every static mode behave identically on either path. Extracting that renderer meant moving 580 lines of colour maths that had never been covered by a test; the behaviour was snapshotted first and compared after, and the snapshot immediately caught a real bug — the OpenRGB path was passing raw configuration where the renderer expected normalised, and every device came out flat grey.
  • Devices that will not accept direct control say so instead of silently swallowing the colours.

Spout and Syphon

The output can be handed to another application on the same machine over the GPU: Resolume, OBS, TouchDesigner, MadMapper, anything that speaks either protocol. No window capture, no plugin, no CPU copy.

It renders into its own hidden window, so the feed continues even when no visualization window is open on any display. Audio capture is deliberately kept alive while this is the only consumer — otherwise the receiver would get a picture that never reacts, with no visible window to explain why.

Two documented facts turned out to be wrong, and both fail silently:

  • texture-bridge types the shared handle as a number. Electron 43 gives { ntHandle: Buffer } on Windows and { ioSurface: Buffer } on macOS, and the two platforms need different calls. Getting this wrong raises no error at all — the receiver just shows a black frame. The conversion is therefore an isolated, pure module, which is also the only test coverage macOS has.
  • A handle beyond the safe-integer range would round silently and point at some other object, so it is rejected rather than sent.

Not available on Linux. Spout is a Windows technology and Syphon a macOS one, and Linux has no built-in equivalent. The card is not hidden there — it explains why and points at the OBS browser source, which works everywhere.

What is verified, and what is not

Everything above passes npm test (808 unit tests) and the GPU self-test. Spout was measured end to end on Windows: the sender registers with Spout, sends 30 frames a second with none dropped, and deregisters on stop.

Honestly, the following have never run on real hardware, and this is recorded in ROADMAP.md rather than left for you to discover:

  • The macOS and Linux packages are built and structurally verified in CI, and the audio engine loads on both. Neither has been launched on an actual desktop.
  • OpenRGB is tested against a protocol-level server, not against real devices.
  • Syphon shares Spout's code path and has never run on a Mac.

macOS packages are unsigned — the Apple Developer Program has not been purchased. macOS quarantines unsigned downloads and reports them as damaged; right-clicking and choosing Open does not clear that on macOS 15 or later. Clearing the flag once with xattr -dr com.apple.quarantine is the whole workaround, and it is spelled out at the top of these notes.

Fixes found while building this

  • The Windows icon test asserted on a generated build artifact that is not in version control, so CI could never pass on a clean checkout — all three runners failed in under a minute.
  • linux.desktop had a shape electron-builder rejects, and nothing on this machine could detect it because Linux packaging never ran here. It is now validated against electron-builder's own JSON schema, and the check was proven by feeding it the known-bad value.
  • An unescaped apostrophe in one English string broke the parse of i18n.js in the browser, leaving the entire interface untranslated. The self-test reported 698 untranslated strings; the cause was one character. Every source file is now parsed in a unit test.
  • Three shared modules declared top-level const in classic scripts, which share one global scope with the preload bridge. One collided with window.api and the file never loaded at all.
  • The documented test count was wrong in three places and had never broken a build. The runner now compares every count claimed in the documentation against the count the run actually produced, and fails if they disagree — including when it cannot read the total, since a check that can skip itself silently is not a check.

3.1.0 — Timeline, Clip Deck, accidental-close protection, Electron 43

Choose a tag to compare

@CaYatur CaYatur released this 02 Sep 23:46

Two performance surfaces that share one clock and one quantiser, accidental-close protection, and a move to Electron 43.

Timeline

Tracks holding clips, and automation lanes that write into the same targets the modulation engine already exposes — anything modulatable is now also automatable.

  • Transport with play, pause, stop, a loop region, and scrubbing that updates the scene immediately, including while paused.
  • A zoomable canvas ruler reading in seconds and in bars. Grid density follows the zoom, so it never turns into noise zoomed out or empty space zoomed in, and zooming holds the instant under the cursor still.
  • Clips move by dragging and trim from their edges, snapping to bar, beat, half, quarter or frame. Hold Alt to suspend snapping mid-drag.
  • A keyframe editor reusing the modulation engine's curve set, so a curve named the same behaves the same in both places. Automation curves are drawn by sampling per pixel rather than joining keyframes with straight lines, which would show a shape the engine does not play.
  • Named markers, jumpable, importable from an LRC or SRT file through the existing lyrics parser.

Seconds are the single source of truth and bars are derived from a tempo map. Storing both would eventually let them disagree with no way to tell which was right. Conversion accumulates across tempo changes, because multiplying by one constant BPM shifts everything after the first change and the error only shows during a show.

Clip Deck

  • A sparse grid: empty slots are not stored, so an 8×8 deck holding one clip writes one record rather than sixty-four.
  • Beat-quantised launching from one frame up to four bars, with a countdown on the armed slot. Launching exactly on a grid line waits for the next one — otherwise hitting the beat squarely makes quantisation look random.
  • A cut is a true cut, with the fade forced to zero rather than left to the drawing side. A fade reuses the existing 18 transitions.
  • Launching a column fires the whole row on one frame, aligned to the longest quantisation in that row so nothing staggers.
  • Follow actions — stop, loop, next, random in column, go to a slot — which chain, with a per-frame fire cap so a chain of near-zero-duration clips reports an overrun instead of pinning the machine.
  • A performance view with nothing but the deck, transport and blackout: large targets, high contrast, every slot reachable from the keyboard.
  • Deck activity records back onto the timeline as tracks, turning an improvised set into an editable one.

Sources are picked from the scenes, templates and colour presets that actually exist, by name. Nothing has to be typed from memory.

Accidental-close protection

Two switches in Settings, both off by default.

With protection on, a visualizer window that closes unexpectedly reopens at once. Recovery keys off intent, so closing from the panel, from the remote or with Esc is never undone. A second switch locks Esc, leaving the panel's Close button and Ctrl+Alt+Shift+Esc as the ways out — a visualizer that cannot be closed would be worse than one that closes by accident. Recovery gives up after four attempts in ten seconds and says so, rather than pinning the machine on a window that dies as fast as it is recreated.

Electron 43.5.1

33.4.11 reached end of life in April 2025 and shipped Chromium 130; this release runs Chromium 150 and Node 24. It is also the floor for the Spout and Syphon senders planned for v3.1.1, which need Electron 40 or newer.

Two things had to change with it. Electron 35 altered the console-message event so that level became a string where it had been an integer, and both self-test listeners compared level >= 2 — which a string silently fails. The self-test would have stopped collecting renderer errors and passed by not looking. Separately, the self-test reported failures but still exited 0, because app.quit() does not consult Node's exit code, so no automation could ever have gated on a smoke failure. Both are fixed and both directions are measured.

Determinism

Offline export is this project's visual regression net, and it only works because every time-dependent source derives from the draw clock. The timeline obeys the same rule: automation evaluated at a time is the same value however the playhead reached it, and the exporter derives time from the frame index alone. The live path uses a separate wall-clock anchor so that several visualizer windows compute the same playhead in closed form — without a message per frame, and without drift between them.

Fixes found while building this

  • The visualizer is fullscreen and always-on-top, so it covers the admin panel, and Chromium stops requestAnimationFrame in a covered window. The timeline and deck would have stopped advancing exactly while a show was running.
  • Taking the transport object and calling play() on it did not refresh the clock anchor, so every programmatic caller — including the new MIDI and OSC transport actions — would have failed silently.
  • The add-track and add-marker buttons did nothing, with no warning: the model was rebuilt every frame, so edits went onto an object that had already been discarded.
  • Firing a slot wrote the transition setting and left it there, so one Cut slot disabled the user's scene transitions permanently.
  • A slot with a name but no source would arm, count down, fire and change nothing.
  • The English-translation gate only scanned what was in the DOM, so any panel that defaults to off could ship untranslated without anyone noticing.
  • The timeline could not be used at all: the button that creates a clip was drawn only when a clip was already selected, so on an empty track there was nothing to select and the first clip could never be made. Clip tracks now carry their own + Clip button, double-clicking empty space adds one, and an empty timeline spells out the three steps instead of showing a bare ruler.
  • The loop could have its start set from the playhead but not its end, and a loop whose end is not past its start disables itself in silence.
  • followPlayhead was in the configuration but never implemented, so playing let the playhead run off the edge and the timeline looked frozen.

Not in this release

Written down rather than dropped quietly: multiple decks, the timeline arming deck slots, multi-select on the timeline canvas, editing a full tempo map through the interface, rendered clip thumbnails, and applying video, image or shader slots — those have to target a layer, and doing it without naming one would overwrite whatever is there.

Verification

703 unit tests, the GPU self-test, and the packaged build's own self-test all pass. The self-test asserts, in the real application: seven accidental-close scenarios, automation reaching the visualizer draw loop, the show clock anchor arriving there, the panel transport advancing, a quantised launch landing exactly on a bar line, an armed slot firing, the panel buttons persisting their result to the configuration, the source pickers listing real scenes by name, and the user's transition setting surviving a deck slot.

Please check on real hardware: the white flash on opening a visualizer. electron/electron#45774 is closed as not planned and the workaround survived the Electron upgrade in the self-test, but nothing here can verify it across your actual monitors.

3.0.0 — Modulation, deep analysis, MilkDrop, mapping, transitions

Choose a tag to compare

@CaYatur CaYatur released this 02 Sep 11:05

CAYADEV Visualizer 3.0.0 is the largest release the project has had. It adds a
modulation matrix, deep audio analysis, a real MilkDrop preset engine,
projection mapping, scene transitions, recording, text and lyrics, a broadcast
layout family, and a rebuilt spectrum engine — and it fixes several things that
were quietly broken.

New engines

Modulation matrix. LFOs, envelope followers, sample-and-hold and random,
routed to any value in the configuration. Eight LFO shapes, rate in Hz or in
beat divisions locked to the detected tempo, curve shaping, per-route smoothing
and slew limiting, and eight assignable macro knobs. LFO phase is computed from
the draw clock rather than accumulated, so offline export stays deterministic
even when frames are skipped.

Deep audio analysis. Musical key and chord from a constant-Q chroma vector
computed with a Goertzel filterbank — FFT bins cannot resolve the bass register
at 2048 samples. YIN pitch tracking, harmonic/percussive separation, per-band
onset detection, spectral descriptors, loudness, dynamics, true peak, stereo
width and correlation. Every measurement is available as a modulation source.

MilkDrop. The preset language actually runs: tokeniser, parser, compilation
to JavaScript closures, the variable pool, and per_frame / per_pixel
equations driving a real warp mesh with feedback. .milk import handles
multi-file packs and reports compile errors per file. No preset text reaches
generated code — identifiers become pool indices — and a fuzz test asserts a
preset cannot smuggle JavaScript.

Projection mapping. Corner pin as a true homography, with the denominator
written into gl_Position.w so the texture stays perspective-correct. Mesh warp
on a Catmull-Rom grid that passes through its control points. Per-output crop,
colour correction and polygon masks, alignment grids and test patterns, and soft
edge blending whose curves are tested to sum to exactly 1 across the overlap.

Scene transitions. 18 of them — crossfade, dissolve, wipes, slides, luma
wipe, glitch, zoom, blur, flash — and they can be switched off. They fire on a
change of scene, not on a change of any setting, so dragging a slider never
starts one.

Recording. One-key capture of the live output as it appears, with the live
audio, modulation, transitions and effects. MP4, WebM, GIF and PNG. GIF uses
two-pass palette generation because one pass bands visibly. PNG snapshot up to
4×.

Text and lyrics. Audio-reactive typography with per-character response, LRC
and SRT import with format detection and enhanced word timings, a timing editor
that writes back to LRC, and now-playing metadata.

Rebuilt spectrum engine

Bars are measured rather than guessed. Four frequency scales (logarithmic,
linear, mel, bark), a decibel amplitude scale with a settable floor, separate
attack and release ballistics that are frame-rate independent, neighbour spread,
symmetric profile smoothing, and a dB-per-octave tilt.

This came out of a real defect: several of the leftmost bars rose and fell as
one. Every bar was given an integer range of FFT bins on a logarithmic split,
and at 2048 samples and 48 kHz a bin spans 23.4 Hz — starting from 30 Hz with 64
bars, the first five bars all resolved to bin 1 and read the same number. Bands
narrower than a bin are now interpolated at their centre frequency.

Music video layouts

Eight restrained layouts for release videos and official channels, deliberately
separate from the club and VJ material: a limited bar count, calm colour, the
logo beside the bars rather than behind them, and the track and artist beneath.

Bars gained real placement — width, horizontal position, height and baseline,
each a fraction of the frame — so the block can sit in a corner, across the
centre, or as a thin strip. All four default to the previous behaviour exactly.

More of everything

  • Visualizer modes: 32 → 48, including 14 generative modes.
  • Backgrounds: 19 → 31.
  • Post-FX: 15 → 40, orderable, audio-bindable, and available per layer.
  • Formulas: 35 → 98, plus 13 solids — platonic, geodesic, L-systems and IFS
    clouds.
  • Built-in shaders: 5 → 42, all compiled on a real GPU by the self-test.
  • Templates: 72 in nine groups.
  • Layer masks, groups with a single fader, solo/mute/lock, and an A/B
    crossfader
    on an equal-power curve.

Fixes

Video files in the media layer never played. The path sat in the URL's host
position, and a Windows path carries a colon and backslashes, neither legal in a
host. Chromium refused to build the request at all, so it never reached the
protocol handler and no error surfaced. The address now carries the path in the
path component, serving honours Range requests — required for seeking and for
MP4s whose index sits at the end — and the OBS overlay is fixed too.

The topmost layer was drawn at the bottom. The array is bottom-first by
design, but the panel enumerated it forwards. Only the presentation is reversed;
reversing the array would have shifted layer groups, the crossfader and mask
targeting, which all index into it.

3D attractors rendered off-screen. Framing came from hand-written constants
that were simply wrong — Lorenz declared a centre of [0, 0, -25] when its
measured centroid is [0, 0, +26]. Framing is now measured from the system's own
bounding box, and a test asserts every attractor lands inside the view volume.

Reset to defaults was missing on every panel added this cycle, because those
panels declared no configuration roots. They do now, and the self-test verifies
the declared roots exist.

The screenshot generator and the self-test opened the webcam if the saved
settings happened to have the media layer on. Camera access is now refused
outright in automation mode, at the permission layer, and the self-test asserts
it stays refused.

Fixes after the first 3.0.0 build

Record and Snapshot could not be pressed. The DOM helper set every property
with setAttribute, so disabled: false became disabled="false" — and in
HTML a boolean attribute disables an element by being present, whatever its
value. Both buttons were therefore dead from the moment they gained a disabled
state, as was the MilkDrop .milk import button. Boolean attributes are now
written by presence, and the self-test asserts it against a real DOM.

The blackout fade did nothing until it ended. Engaging blackout turns every
layer off, which left the layer stack resolving to an empty list and drawing a
fully transparent frame. The crossfade was mixing the outgoing scene into
nothing, which looks like nothing: the old scene stayed at full brightness for
the whole blackout duration and then snapped to black at the last moment.
Measured across a one-second fade, mean luminance was flat at 127.8 and then
0.0; it now ramps 127.8 → 103 → 77 → 50 → 22 → 4 → 0. An empty stack falls back
to the composition's black ground. Transparent broadcast output is the one
exception and still composites with alpha 0, so OBS overlays are unaffected.

Blackout from the phone remote did nothing when the layer stack was on. The
remote wrote background.type and visualizer.type directly, but with the
layer stack enabled the scene is drawn from the layer list, so neither value
reached the output — the panic button failed in the one situation it exists for.
The remote now sends an action and the panel performs the blackout, which is
where the single real implementation and its restore state already live. The
remote's allowed-path list stays narrow; a phone still cannot rewrite layer data.

Scenes and colour presets could not be reordered. Items sat in creation
order, and the only way to change it was to delete and recreate them. All three
lists — the scene dock, the scene list in the Library card, and the colour
presets — now have a drag handle. The handle is what is draggable rather than
the whole row, because each row carries a text input for its name and a
draggable container stops text selection inside it. The handle is also a button,
so the arrow keys reorder while it has focus; drag and drop on its own would
leave reordering impossible without a mouse.

Snapshot never worked, and recording produced an empty file. Snapshot redrew
the scene and passed SVPreview.lastAudio or window.SVAudioCore as the audio
frame; neither has ever existed in this project, so every snapshot drew with
audio === undefined and the visualizer modes threw on their first read. It now
captures the frame already on screen instead of redrawing it.

Recording read the live preview surface directly, and that surface is resized
whenever the preview relayouts — which the record card itself causes, since its
status line changes the card's height. Resizing a canvas with an active
captureStream ends the track, so MediaRecorder collected nothing and ffmpeg
reported "streams received no packets" over an empty container, after the user
had already chosen a filename. The scene is now copied each frame onto a
separate canvas whose size never changes during a capture, and the stream comes
from that.

That canvas also fixes the resolution: capture used to be the size of the
preview surface, so snapshots came out around 600 px wide and the scale slider
only enlarged them without adding detail. There is now a resolution setting —
1080p by default, up to 4K, plus portrait, square, and "preview size" — applied
to both recording and snapshot, with the aspect ratio preserved and the
remainder left black. The source is still the preview, so scaling up adds no
detail; Video Export remains the way to render at genuine high resolution.

Eleven untranslated strings in the English interface, including composed
labels such as "Visualizer · Bars", "+ Sprites" and "1. Threshold" ...

Read more

2.1.0 — Layer compositing, GPU effect chain, 3D geometry, tempo & DMX

Choose a tag to compare

@CaYatur CaYatur released this 01 Sep 21:39

This release adds the engines a VJ rig is actually built on: a layer compositor, a 15-effect GPU chain, a 3D geometry engine driven by 35 verified mathematical formulas, BPM detection with an auto-VJ, and Art-Net/DMX output.

Everything is verifiable: npm test runs 41 unit tests, and npm start -- --smoke exercises every mode, background, effect, and formula on the real GPU.


⬗ Layer compositing

The scene is no longer a fixed stack but an ordered list of layers. Each layer has its own source (background, visualizer, media, visual objects, logo), blend mode, opacity, transform, audio response, and its own settings — so one scene can hold two bar layers in different colors, or a mandala laid over a shader.

  • 17 blend modes — add, screen, multiply, overlay, difference, color dodge/burn, hard/soft light, hue, saturation, color, luminosity…
  • Per-layer scale, rotation, position, mirroring
  • Audio-driven opacity / scale / rotation modulation with band selection
  • Reorder, enable, and disable layers from the panel

Nothing breaks. With an empty layer list the scene is synthesized from the legacy fields, so v2.0 settings, scenes, and preset packs render identically. That was verified frame-by-frame against a pre-refactor export.

✦ Post-processing effect chain

15 GPU effects applied in order to the composited scene: bloom, chromatic aberration, glitch, film grain, CRT/scanlines, pixelate, kaleidoscope, mirror, color grade, vignette, trails/echo, edge highlight, zoom blur, ripple distortion, posterize.

The order changes the result, every parameter can be bound to an audio band, and the chain runs the same way on export.

Only users of the feature pay for it: with an empty chain the scene is composited by the browser's GPU compositor; adding the first effect switches the engine to a single merged surface.

◈ 3D geometry from 35 mathematical formulas

Audio-reactive geometry in true perspective, in four families:

Family Examples
Surfaces (12) Sphere · Torus · Klein bottle · Möbius strip · Supershape (Gielis) · Boy's surface · Dini surface · Seashell · Spherical harmonic · Chladni pattern · Ripple surface · Plane
Plane curves (12) Lissajous · Rose curve · Epicycloid · Hypotrochoid · Superformula · Butterfly · Lemniscate · Astroid · Cardioid · Phyllotaxis · Logarithmic spiral · Harmonograph
Space curves (4) Torus knot · Helix · Viviani curve · Trefoil knot
Attractors (7) Lorenz · Rössler · Thomas · Aizawa · Halvorsen · Clifford · de Jong

Surface / wireframe / point rendering, four deformation modes, four coloring modes. Each formula's own parameters become sliders automatically.

On the accuracy claim. 30 of the 35 formulas are closed-form, and every one of them is checked in tests/formulas.test.js against values derived by hand from the definition — that the Viviani curve stays on its sphere, the torus tube radius, the m↔n antisymmetry of Chladni patterns, the bounds of the Clifford and de Jong maps. A catalog test prevents a formula marked "closed form" from being added without a test. The remaining 5 are numerically integrated attractors and are labeled as such, with the badge visible in the panel.

The engine uses its own matrix math — no third-party 3D library. The mesh is built once and stays on the GPU; audio deformation happens in the vertex shader, so even a 96×96 surface costs nothing per frame on the CPU.

🥁 Tempo and Auto VJ

BPM is estimated from a period histogram of beats found via spectral flux. Every beat pair in the last 8 seconds votes, so a single missed beat cannot halve the tempo. Tap tempo and a BPM lock are included.

Measured: 90 / 120 / 128 / 140 / 174 BPM → 89.8 / 120.4 / 127.9 / 140.0 / 173.7

Auto VJ switches scenes, visualizers, or color presets every N bars or seconds; in bar mode the transition is aligned to the beat.

🎚️ Art-Net / DMX output

Scene colors go out over the standard protocol (ArtDMX, UDP 6454) to lighting consoles, DMX interfaces, and software such as QLC+. Four color sources (scene palette, frequency bands, rolling spectrum, single color), RGB/RGBW fixtures, universe and channel settings.

It does not replace Windows Dynamic Lighting: that drives consumer devices, this drives stage lights.

🎨 58 color presets

Up from 10, in seven groups — Classics, Warm, Cool, Neon & Cyber, Dark, Light, Monochrome Families. They apply to every background, the 3D engine, and Studio shaders alike. The mobile remote now reads the same list instead of keeping its own copy.


🐛 Fixes

  • BPM detection halved every tempo above ~125 BPM — that is most dance music. The histogram binned in BPM space, where fixed frame-quantization jitter spreads wider at higher tempos, so 128 BPM lost to 64. Binning now happens in period space. Found by writing the test.
  • Video export could not resolve Studio presets, so a scene using a custom mode rendered blank into the MP4.
  • The phone's ▶ Open button opened on a single display even when several were selected.
  • A mobile remote connected on its own kept audio capture running with nothing consuming the frames.
  • Preset names ending in "(copy)" were not translated in the English interface.

🧪 Verifiability

npm test               # 41 unit tests — formulas, tempo, Art-Net packets
npm start -- --smoke   # every engine on the real GPU

The self-test opens every registered visualizer mode and background, compiles every built-in shader and every post-processing effect on the GPU, draws all 35 formulas, exercises multi-display and blackout, renders a real MP4 and checks it is not black, and finally switches the interface to English and scans for untranslated text.

🗺️ Not included

NDI, Spout/Syphon, the complete MilkDrop engine, WebGPU, and Ableton Link are not in this release. What was done instead, and why, is written out plainly in ROADMAP.md.


📥 Download

File Description
CAYADEV Visualizer Setup 2.1.0.exe Windows installer (recommended — registers the identity needed for background Dynamic Lighting)
CAYADEV-Visualizer-2.1.0-portable.exe Portable build (no installation; controls lighting only while the app is focused)

macOS packages must be built on a Mac with npm run dist:mac.

2.0.0 — OBS streaming output, Studio shader editor, multi-monitor

Choose a tag to compare

@CaYatur CaYatur released this 30 Aug 10:58

This release turns the app from a visualizer into a streaming and VJ tool: an overlay you add to OBS, a shader editor for writing your own effects, a phone remote, MIDI/OSC control, and true multi-display support.


📡 Streaming Output — OBS and the browser

The page you add to OBS as a Browser Source runs exactly the same engine as the desktop window. There is no second renderer, so the stream never drifts away from what is on screen.

  • No plugin to install — OBS → Sources → + → Browser → paste the address
  • Real transparency; append ?transparent=0 to put the background on stream too
  • ?fps=30 / ?scale=0.75 lowers the load of that one source without touching the rest
  • Works even if you never open the visualization window — capture starts when the browser source connects
  • Listens on 127.0.0.1 only by default; opening it to the network requires a token on every request

📱 Mobile remote control

Scenes, color presets, and Studio presets are listed by name, the active one is highlighted, and every list can be stepped through in order with ◀ ▶. Open/close the visualization, switch modes, adjust sensitivity, and black out with one button. The page follows the application's language.

🧪 Studio — build your own visualizer

  • Variation: name and store the look you have on screen — no code
  • Shader: the same entry point as Shadertoy (mainImage), with audio uniforms on top — sv_bass, sv_spec(x), sv_waveAt(x), sv_col(x), sv_prev, sv_media
  • Editor with line numbers, syntax highlighting, a live preview, and a marker on the line the compiler error is on
  • Define your own sliders — the panel generates the controls automatically
  • Import: Shadertoy · ISF · MilkDrop .milk · .svpreset / .svpack
  • No service is contacted; no account or API key is required

🎨 15 new visualizers + 8 new backgrounds

Now 31 visualizer modes and 19 background types.

New modes: Kaleidoscope · Helix (DNA) · Liquid Blobs · Fireworks · Vortex · Mandala · Skyline · Lightning · Ripple Grid · Lissajous · Strings · Bubbles · 3D Wave · Arcs · Pinwheel
New backgrounds: Ink · Nebula · Hex Grid · Mosaic · Corridor · Spiral · Snow/Embers · City

Plus the ♾ Feedback engine — that classic MilkDrop "endless tunnel" family.

🖥️ True multi-monitor

A separate full-screen visualization opens on every display you select, all at once. ESC closes them all.

🎛️ MIDI and OSC

Map your controller's CC/note messages or OSC addresses to any setting or action. Press Learn and move the control — channel and number are filled in for you.

🎥 Media layer

Your webcam or a video file becomes a scene layer: kaleidoscope, hue shift, audio-driven zoom. Studio shaders can read the same image as sv_media.

✨ Scene Generator

Describe a mood and the application builds the scene. Entirely offline — not a neural network: the text is reduced to four axes (energy, warmth, brightness, texture) and the scene is built by a deterministic generator seeded from them.

🌑 Blackout

One button in the panel, on the mobile remote, and as a MIDI/OSC action. It does not close the visualization — press again and the previous look (background, visualizer, logo, media) comes back exactly as it was.


🐛 Fixes

  • Saving settings.json in a text editor (Notepad adds a BOM) silently reset every setting
  • The Dynamic Lighting control-status text was not translated in the English interface
  • Video export could not see Studio presets — a scene using a custom mode rendered blank into the MP4

🌍 Language

Every new interface ships in English and Turkish. The interface language is now reported to the main process as well, so system dialogs and the pages served by the streaming server follow the application's language rather than the OS locale.

🗺️ Not included

NDI, Spout/Syphon, and the complete MilkDrop engine are not in this release. What was done instead, and why, is written out plainly in ROADMAP.md.


📥 Download

File Description
CAYADEV Visualizer Setup 2.0.0.exe Windows installer (recommended — registers the identity needed for background Dynamic Lighting)
CAYADEV-Visualizer-2.0.0-portable.exe Portable build (no installation; controls lighting only while the app is focused)

macOS packages must be built on a Mac with npm run dist:mac.

1.3.1

Choose a tag to compare

@CaYatur CaYatur released this 06 Aug 21:15

Bug-fix release on top of 1.3.0.

Fixed: deleting a scene or resetting a section froze the app

Both actions went through window.confirm, which blocks the entire renderer thread. On a multi-monitor setup the system dialog can also open on a different display or behind the window, so the app appeared to crash and the action never happened.

Every confirmation and notification in the panel is now in-app and non-blocking — 18 call sites in total. The live preview keeps running while a confirmation is on screen.

Fixed: Wave drew a flat line on wide and scaled displays

Wave stepped one sample per pixel through a 2048-sample buffer. On any canvas wider than that — including scaled displays, where a 1707 px screen at 150% is 2560 device pixels — the index ran off the end and the last sample repeated, leaving a flat line across the rest of the screen. It now samples the buffer proportionally. Verified across a 3845 px canvas with no flat segment.

Finer resets and live indicators

  • Reset a single setting from its own row — no need to reset the whole section.
  • The "changed from default" dot and the section/category counters now update while you drag, instead of only after leaving and re-entering the category.

New application settings (⚙ menu, both off by default)

  • Keep Visualization Always on Top — the visualization stays above other windows; it re-raises itself when it loses focus and on a timer, so another app cannot cover it.
  • Extended Setting Ranges — raises slider maxima so you can enter values far above the normal range. Settings genuinely bounded by the algorithm (smoothing, background resolution) are excluded, and high values you already entered survive turning it back off.

Also

  • The default background type is the fluid gradient again.

1.3.0

Choose a tag to compare

@CaYatur CaYatur released this 06 Aug 20:09

Redesigned admin panel

  • Three-column workspace — category rail, setting cards, and a dock with a live preview, audio meters and scenes.
  • 5 categories (Scene, Audio, Lighting, Output, Library) with a Basic / Advanced split, so the panel stays readable as the app grows.
  • Live preview running the real rendering engine inside the panel. It is driven by a sample signal, or by real captured audio when you click the Demo badge — no need to open the visualizer.
  • Search (Ctrl+K) across every setting; picking a result opens its category and highlights the control.
  • Scenes — save the whole look (background + visualizer + logo + visual objects) under a name, restore with one click, export/import.
  • Badges showing which settings differ from the defaults, plus per-section and per-category resets.

Rendering performance

Measured at 1080p on a 75 Hz display:

Before After
Gradient + bars with glow 51.7 FPS, p95 27.1 ms 75 FPS, p95 13.5 ms
Frame rate set to "60" 37.3 FPS 60.0 FPS
Frame rate set to "30" 25 FPS 30.0 FPS
  • Frame pacing fixed. The old threshold check dropped well below the requested rate whenever the limit was not an exact divisor of the refresh rate. An accumulator now holds the true average, and a new Match Display option renders one frame per refresh for perfectly even spacing.
  • Single-pass bloom replaces per-shape canvas shadowBlur, which was performing 70–140 separate blurs per frame.
  • Background pixel sampling (a synchronous readPixels that stalled the GPU ~31×/second) now runs only when Dynamic Lighting actually consumes it.
  • The fragment shader no longer computes noise octaves that were immediately discarded.

New modes

10 new visualizer modes (14 total): Segments, Dot Matrix, Ribbon, Terrain, Radial Wave, Rays, Tunnel, Orb, Particles, Spectrogram.

8 new background modes (10 types total): Wave Layers, Aurora, Starfield, Retro Grid, Bokeh Lights, Digital Rain, Network, Pulse Rings.

Every background type now has its own detailed settings (star count, horizon height, curtain thickness, link distance, ring rate …). All modes share the existing color pickers, built-in presets and your saved presets.

Fixes

  • Saving a scene did nothing — Electron does not implement window.prompt, so the handler aborted silently.
  • Taskbar icon was not transparent — the ICO stored every size as PNG, which Windows renders opaque below 256 px. Sizes up to 128 px are now 32-bit BMP with an alpha channel.
  • Importing a settings backup destroyed saved scenes. Scenes and color presets are now excluded from the backup and preserved on import.
  • The display selector rendered blank when the previously saved display was no longer connected.
  • Opening the visualizer started audio capture twice.