Soniflux
In progress. Every screenshot on this page is the “Bless Your Heart” rebuild project, September 2026, running on the machine that holds the GPU. The take nodes are wired to reference stems as stand-ins until the real takes are recorded.
Soniflux is a digital audio workstation that runs in a browser tab, built around AI nodes instead of around a track list. I built it to replace the way I had been making music for the last year: record a take, upload it to one AI tool, download the result, upload that to another, download again, drag everything into a DAW, and start over the moment I wanted to change a line.
The songs are for Cywren. She is a character I made up to write songs for. The first one came out better than I expected, so I kept the same voice prompts and kept writing, and each song got a little easier to produce than the last. At some point that turned into a proper project: give her a voice, motives, a personality, write those into the lyrics and the feel of every track, and build an album around her. Soniflux is the studio that album gets made in.
The idea is simple to say. I sing and play; the graph makes it sound like the record. My dry takes go in on the left, pass through nodes that clean them, tune them, reshape them and, where I ask for it, re-sing them in her voice, then land on a console where I mix them the way I would mix anything else, and bounce.
Six months, by hand
The first three Cywren songs took most of six months. The lyrics and the basic melodies were in my head from the start; the time went into the pipeline. Every song I changed some of the tools, updated the scripts that strung them together, generated parts, and then assembled the parts in a DAW by hand. Almost every song was built through that workflow, and none of it carried over cleanly to the next one.
6 months, 3 songs
across a different set of tools each time. The melody was never the slow part.
What changed was a conversation. I pointed Claude Fable at the scripts and the process, spent about thirty minutes describing what I actually did to a song and where it hurt, and then asked for one tool that could do all of it in the browser: assemble my recordings, pass them through the AI steps, and hand the result back to a console where I could polish it. Everything below came out of that request over the following days, in milestones I reviewed one at a time.
The part I care most about is the middle. If I do not like a sound or want to change a lyric, I do not go back to the start. I chain a different set of nodes onto the take and run it again.
The graph is the session
There is no track list. A song is a graph: source nodes on the left, a console in the middle, buses and a master chain on the right, and every wire is a signal path you can see. The graph is the only thing the project file stores, and the audio engine is compiled from it. That one decision is what lets the same graph drive live playback in the tab and a deterministic offline bounce on the server, because there is exactly one description of the mix and two things that read it.
Thirty-seven node kinds exist today, and every one of them has a working DSP implementation behind it; a kind added to the schema without one is reported as an error rather than passing audio through silently. They fall into sources (a file, a tone, a step-sequenced drum kit), filters and EQ, dynamics, colour, time and pitch, the AI nodes, the console and its buses, sends and VCAs, and outputs. Double-click any node and a device panel opens over the canvas with its full controls.
From a take to her voice
This is the loop the whole tool exists for. One vocal take, start to finish:
- Record dry Close mic, no reverb, no tuning. The take goes into a File Source node. Bass is a DI; synth is a line out. The amp, the width and the sidechain pump all live in the graph.
- Clean it A Stem Split node runs Demucs on the take and keeps only the vocal, so room noise, bleed and the click track are gone before anything else touches it.
- Put it in tune and on the grid Tune analyses the pitch, snaps each note to the key or to a harmony above it, and writes the correction as an automation lane on a Transpose node. Quantize does the same for timing against the tempo map. Both are curves you can edit by hand afterwards.
- Re-sing it Prompt Regenerate is audio-to-audio: my take goes in, a prompt describes her, and a strength control decides how much of the input survives. Low strength keeps my phrasing and inflection and changes the timbre; high strength keeps only the shape. Generate on the ACE-Step engine takes lyrics with verse and chorus markers and sings them outright, which is how a line I have only hummed gets words.
- Iterate Do not like the second line? Change the prompt, or sing it again with the inflection I want, and run that one node. Nothing downstream has to be rebuilt, because downstream is just wires.
- Bend it High-pass, gate, de-ess, compress, EQ, a little tape. The same chain a mix engineer would build, as nodes.
- Mix and bounce Into a console strip, onto a bus, through the master chain, out to a file rendered on the server.
The backing vocals are the clearest example of what a graph buys you. I sing the chorus hook once, in unison. One node cleans it, and its single output feeds three: a three-voice chorus for the double, a transpose up a third for the high harmony and a transpose down a fourth for the low one. Three parts from one take, and if the take changes, all three follow.
The AI nodes, and the GPU they share
Four kinds do the AI work, plus two analyses that decide how far to move a take rather than generating anything.
- Stem Split
- Demucs, four models to pick from. Each stem is its own output port, so you can wire only the vocals on and leave the rest unconnected.
- Generate
- Text to audio. Stable Audio Open by default, ACE-Step when there are lyrics to sing. Pieces longer than a model can make are built from overlapping passes with one seed per pass, so a seeded piece is the same piece every time.
- Prompt Regenerate
- Audio to audio. The incoming stem plus a prompt, with strength from 0 (keep the input) to 1 (ignore it).
- Analyze
- Tempo and key from the audio. It seeds the tempo map; the key it reports on a whole mix is known to be off by a fourth, so the bass gets a vote.
- Tune
- Pitch correction as a decision, not an effect: where each note sits and how far to move it, handed back as an automation curve on a Transpose.
- Quantize
- The same for timing, against the tempo map.
Every one of these runs on the server, on an RTX 3090 that is not in the same room as the laptop. The tab submits a job, follows its progress over a stream, and pulls the result into the graph as an asset when it lands, so a closed tab does not lose the work. Two things had to be true for that to be livable.
The first is that the GPU is leased, not grabbed. A broker hands out time on the card with a deadline, and a job that arrives while another is running waits in a queue with a position it can report, rather than being refused or, worse, being admitted and crashing the job already there when the VRAM runs out. A model's weights are fetched before the lease is taken, so the download never counts against it, and a driver that overruns its deadline is stopped, not asked nicely.
The second is that the heavy models do not run inside the service. ACE-Step pins versions of its dependencies the service cannot share, so it runs as a subprocess in its own environment, with a contract that is one progress line per stage and one file at the end. The first generation on the box ran to completion and then failed on the very last line, saving the file; the fix was a shim, and the lesson was that the contract has to include the save.
Bending each track
After the AI has had its say, the rest is ordinary mixing, and the nodes are the ones a mixer would reach for. The lead vocal chain in the rebuild is a high-pass at 80 Hz, a gate, a de-esser at 6.5 kHz, a 4:1 compressor, a four-band EQ and four decibels of tape at forty percent mix. Every parameter can be automated, and automation is scheduled onto the audio parameters themselves rather than driven by a timer, which is why it works identically in the bounce.
Two controls on every node matter more than the rest. Bypass is an edit to the graph, not a flag inside the node: a bypassed node is left out of the compiled plan and its input is wired straight to wherever its output went, so it costs nothing and, importantly, contributes no latency that the rest of the mix then has to be delayed to match. Freeze renders what a node puts out, including everything upstream that feeds only it, stores the result as an asset and swaps the node for a player of it. It exists because the live graph runs on one audio thread, and this song’s five oversampled saturators and two phase-vocoder transposes need about 122% of a core in real time. A mix you cannot audition is not a mix. Freeze all does it for every chain at once and leaves the console live.
The console
The console is one node with a real desk inside it: up to sixty-four strips, four buses, four sends, eight VCAs and a master section. It is built to the way a console behaves rather than the way a plugin chain does, and the differences are the point.
- Every strip has trim, a switchable high-pass, EQ and dynamics with pre and post ordering, four sends each with pre or post, bus and VCA assignment, mute group, meter tap, pan, fader, automation arm, and mute, solo and solo-safe
- Solo has three modes. PFL and AFL replace what the monitor output carries and leave the main mix alone, so soloing a channel cannot ruin a take being recorded; only solo-in-place mutes the others on main
- Monitor dim and mono live downstream of main. The bounce cannot hear the control room, and there is a test that says so
- VCAs scale the fader rather than replacing it, so post-fader sends follow the VCA and pre-fader sends do not
- Buses and sends are output ports, not dropdowns: a wire you can see beats a target that dangles when the node it named is deleted
The bounce
Export renders the graph through the same DSP units you hear, then posts the result to the server for encoding. By default the render itself runs on the server too, because that is the machine the audio already lives on: only the graph crosses the network, and the bounce survives a closed tab. The browser is the fallback.
- The graph is recompiled at the export sample rate, because delay compensation is solved in samples and a plan built for 48 kHz would misalign every compensated edge at 96
- The head of the file is trimmed by the latency arriving at the output, so two bounces of the same music null against each other instead of differing by however long the plugin chain happened to be
- The engine always hands over 32-bit float. Bit depth, dither and loudness normalisation are applied once, by ffmpeg, on the server
- A graph can hold several Export nodes and each renders only its own upstream subtree, which is how stems come out
- Every finished bounce is listed in the dialog as a download, because the server is usually not the machine the browser is on
A freeze, a stretch and a bounce are all the same shape of work, so they share one queue that the whole app can see. The node a render belongs to draws its progress along its bottom edge, the Renders panel lists everything in the order it will run with a way to reorder or cancel, and cancelling reaches into the render rather than throwing the result away minutes later. The saturator’s oversampler was the first thing profiled in the repo: it was 87% of a channel’s render cost, and skipping the taps that are zero by construction took a bounce of it from 6.5 seconds to about 2, with a test that holds the new code sample-for-sample to the old.
Two details that earn their keep
Autosave waits three seconds for the graph to go still, so it never lands in the middle of a fader drag, and gives up waiting after thirty, because an automation pass is a continuous edit and would otherwise hold a whole take unsaved. The cadence is a preference of the browser, not a property of the song, so it never reaches the project file or the server.
Nothing in Soniflux deletes anything. Removing a project, a stem or a bounce renames it into a trash folder under a timestamped name the app reports back, so recovery is a move in a shell and no timer ever throws audio away on its own.
Under the hood
A pnpm workspace with three packages, two apps and one Python service. The engine has no idea whether it is running in a tab or in a headless render on the server; both hand it a context and read back a plan.
- packages/schema
- Every node kind, its parameters and the project file, as validated types.
- packages/engine
- The graph compiler, delay compensation, transport, tempo map, automation, freeze and the offline renderer.
- packages/dsp
- The worklet layer: compressor, limiter, saturation with oversampling, reverb, pitch and time.
- apps/web
- The canvas, the device panels, the console, the timeline. React and React Flow.
- apps/server
- Projects, content-addressed assets, the render queue, ffmpeg encoding, downloads.
- services/ai
- FastAPI: separation, generation, analysis and tuning, the GPU lease broker, and the ACE-Step driver in its own environment.
It deploys with one script to the machine that has the GPU and the storage, behind nginx with TLS on both its LAN name and its tailnet name, under systemd. Assets are stored under the hash of their contents and hard-linked rather than copied, so re-importing a 400 MB multitrack costs no disk. A licence gate runs in the build and refuses model weights whose terms the project has not accepted.
Why it looks like this
A DAW hides the signal path behind a track list. That is fine when the signal path is a microphone and a fader. It is not fine when half the path is a model that re-sings the take, because then the interesting decisions are exactly the ones the track list hides.
Soniflux draws the path. A take is a node, her voice is a node, the console is a node, and the wire between them is the whole explanation of how a song got from what I sang to what she sings. When something is wrong, I can point at it. When something is right, I can copy it to the next song. That is what six months by hand never gave me, and it is the only reason this exists.