Flyback is a patchable synthesiser for .NET 10. One graph can generate both a picture and a sound. The visual path and the audio path share the same module graph and are compiled down to the same flat instruction stream.
See the codeFlyback is a patchable synthesiser for .NET 10. One graph can generate both a picture and a sound. The visual path and the audio path share the same module graph and are compiled down to the same flat instruction stream.
The website has screenshots, tutorials and the plugin guide. See CHANGELOG.md for what changed in each release, and the releases page for downloads.
Flyback is mostly written by a large language model (LLM). A human directs the work, decides what ships and writes some of it by hand. That is how the project happens to be made, not a requirement on anyone who works on it.
The project holds to the same standards any modern codebase would: a CI gate that restores, compiles and runs the whole test suite on every push and pull request, signed releases built from the same image the gate stops inside, architecture decisions recorded in docs/adr/, and a changelog per release. Nothing merges that the gate has not passed.
Tokens are the one thing the project spends money on, and a donation goes on that budget. Flyback is strictly non-profit: nothing in it is sold, nothing is behind a payment, and nobody is paid out of it. The address is in About and in the site's footer.
CONTRIBUTING.md is how to work in it.
dotnet run --project src/Flyback.App -c Release
PATH or picked by hand; Motion JPEG AVI and WAV are written by Flyback itself and need nothingThe solution builds the web editor, which needs the wasm-tools workload:
dotnet workload install wasm-tools
dotnet publish src/Flyback.App -c Release -r win-x64 -o artifacts/win-x64
dotnet publish src/Flyback.Cli -c Release -r win-x64 -o artifacts/win-x64
dotnet publish src/Flyback.Viewer -c Release -r win-x64 -o artifacts/win-x64
This produces a self-contained folder with the app, the CLI and the viewer, plus the shared runtime and plugin folders:
Flyback.exe the app
flyback-cli.exe the command line tool
flyback-viewer.exe the viewer: opens a patch and plays it, and writes nothing
Flyback.Core.dll the patch model and the module API, which plugins are built against
Flyback.Engine.dll the compiler, the language and the renderers
Flyback.Plugins.dll shared plugin host
plugins/ platform backends, and the Picture, Voice, Effects and Mastering modules
Supported publish targets include:
win-x64win-arm64osx-x64osx-arm64linux-x64macOS bundles are published as Flyback.app beside the output folder, whose Info.plist declares .fbk, .fbkb and .fbks. On Windows and Linux, Settings → Files registers the editor or the viewer to open them, for the current user.
docker build --output artifacts .
This restores dependencies, compiles the solution, runs the tests, and publishes the self-contained outputs for the default target set.
To choose a specific set of runtimes:
docker build --build-arg RIDS="win-x64 win-arm64 osx-arm64 osx-x64 linux-x64" --output artifacts .
To restore, compile and run the whole test suite without publishing anything, which is what the Build workflow (.github/workflows/ci.yml) does on every push and pull request:
docker build --target gate .
To measure how much of the code the tests run, into coverage/ with a table per assembly in coverage/summary.md and the specs' figure beside it rather than in the sum, the same way the weekly Coverage workflow does:
./coverage.sh
The Release workflow (.github/workflows/release.yml) builds every platform, zips each one, and publishes them with a SHA256SUMS file and its signature, SHA256SUMS.sig. The app only installs an update when that signature checks out against the public key in src/Flyback.Editor/Updates/release-key.pem. The private key is kept in the repository secret RELEASE_SIGNING_KEY, and the workflow fails before it builds anything if the secret is missing or doesn't match the committed public key. It also fails before building if CHANGELOG.md has no ## X.Y.Z heading for the version being released, since that heading is what the "What's new" window reads. ADR-0088 explains the design. The same key signs the packages of the plugins the preset site starts with and hands out, Figures, Fractals and Easy among them, which no release carries (ADR-0141).
Everything but the publishing is release.sh, which runs the same here:
./release.sh 1.4.0
It builds, tests, packs and signs into dist/, with each platform as a folder to run rather than a zip, and publishes nothing. Off GitHub, RELEASE_SIGNING_KEY holds a local test key rather than the release key: release-key.sh takes it from the environment, or makes one and keeps it in the user environment, and only GitHub stops at a key that does not pair with the committed public key or at a missing changelog heading. The preset site's build and a Release run of the site sign with the same variable, and a Debug build checks no keys at all. A build on this machine, release.sh included, embeds the public half of the local key in place of release-key.pem, so a local release installs over local builds, and its runs count as debug in the usage statistics; on GitHub the committed key is embedded.
To make the key pair, with the openssl that comes with Git Bash:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out flyback-release.key
openssl pkey -in flyback-release.key -pubout -out src/Flyback.Editor/Updates/release-key.pem
Commit the public key. Paste the whole contents of flyback-release.key into the RELEASE_SIGNING_KEY secret, keep a copy somewhere safe such as a password manager, and then delete the file. Never commit it. Installed copies only accept releases signed with the key they were built with, so a lost key means everyone installs the next release by hand.
The CLI runs the same engine without the Avalonia shell, so it is useful for rendering, checking and batch jobs.
flyback-cli render nebula.fbk -o nebula.png --size 1920x1080 --at 2.5
flyback-cli render drone.fbk -o drone.mp4 --seconds 30 --fps 30
flyback-cli render drone.fbk -o drone.avi --seconds 30 --fps 30
flyback-cli render drone.fbk -o drone.mp3 --seconds 30
flyback-cli render drone.fbk -o drone.wav --seconds 30
flyback-cli render drone.fbk -o drone.wav --seconds 30 --loudness
flyback-cli render drone.fbk -o drone.mkv --seconds 30 --format mp4 --ffmpeg /opt/bin/ffmpeg
flyback-cli check nebula.fbk
flyback-cli check nebula.fbk --strict
flyback-cli check --preset "Mycelium" --json
flyback-cli compare nebula.fbk nebula-ported.fbk --seconds 30
flyback-cli info nebula.fbk
flyback-cli info --preset "Plasma"
flyback-cli modules
flyback-cli modules adsr
flyback-cli pack nebula.fbk -o nebula.fbkb
flyback-cli pack --preset "Mycelium" -o mycelium.fbkb
flyback-cli pack-plugin Flyback.Plugins.Ripple.csproj -o ripple.fbkp --key ripple.key
flyback-cli plugin-key -o ripple.key
flyback-cli print nebula.fbk -o nebula.fbks
flyback-cli print nebula.fbk --check
flyback-cli print --preset "Whole band"
flyback-cli render nebula.fbks -o nebula.png
flyback-cli probe --keys
flyback-cli shot --preset "Flyback Theme" -o theme.png --at 30.3 --select "Picture: Scope"
flyback-cli probe --provider all
flyback-cli viewer nebula.fbk
render, check, info, print, pack and shot read a patch file, or a shipped or plugin preset named with --preset in its place; --presets lists the names, as a JSON array under --json, and a name nobody shipped is refused with the list.
render: renders a still, a clip or a sound file from a patch. The extension picks the format — .png, .avi, .mp4, .webm, .mov, .wav, .mp3, .m4a, .flac — and everything but .png, .avi and .wav is encoded by ffmpeg, taken from PATH unless --ffmpeg names one. --format overrides the extension, and --loudness prints how loud the sound came out: integrated loudness in LUFS and true peak in dBTP, measured as ITU-R BS.1770 does. The picture is drawn on the GPU through a headless OpenGL context (EGL on Linux, WGL on Windows) where there is one, and on the processor where there is not; --processor asks for the processor, whose picture is exact to the bit, and --gpu fails rather than fall back. On the processor the patch runs compiled; --interpreted keeps it on the interpreter, which writes the same bytes more slowly.check: compiles the patch and reports issues; for a text patch, --json gives each complaint's line, column and a stable codeinfo: shows module and wire counts and compile cost; --preset describes a shipped preset by name, and --presets lists thempack: packs a patch together with the files it references; --preset packs a shipped preset with the files it carriespack-plugin: builds a plugin into a .fbkp, signed with the key --key namesplugin-key: makes the key a plugin's packages are signed with, which every update must be signed with tooshot: draws the editor's window with the patch open into a PNG, with no screen and no sound: --at is the second the picture is of, run up to from a second and a half before; --size is the window's, 1440x900 unless given; --select selects a box or module by name, so the inspector shows it; --canvas shows the canvas for a patch whose text is the document; --crop writes only the canvas around the modules, with 24 pixels of room. Flyback --shot, beside it, does the drawing (ADR-0166)viewer: starts flyback-viewer with everything after the word, so flyback-cli viewer --help is the viewer's own helpprint: writes the patch out as text in the language, and can check that the text builds back to the same program; --preset prints a shipped preset by name, and --presets lists themcompare: plays two patches side by side for --seconds at --size and says whether they are the same instrument, sample for sample and pixel for pixel, and where they first part when they are not; it exits 1 when they differmodules: lists the modules this build has, and which plugin defines each; given one by type id or name, it describes that module: each socket's default and range, where |> lands, what it carries besides its sockets and what it doesprobe: asks an assistant which models it has and what each one acceptsstills: draws a still of every preset into --out, with the index.json the galleries show them by in place of drawing them (ADR-0163); ./stills.sh <folder> lays out the plugins a build ships first, and every release and site build runs itcheck exits with:
0: no errors1: patch errors2: the job could not run--strict makes a warning fail as well. check, compare, info, pack, modules and
probe each take --json, which writes the same answer as a document instead of
as prose.
info says what a patch requires and modules says what is installed to meet
it, which is the pair to reach for when a patch reports that it did not load
completely.
probe is the one command that reaches off the machine. It asks a provider's endpoint what it
offers and records the answer in the settings file both programs read, so the app's model box
fills itself in without being told. It takes minutes and the provider bills for it, so it says
what that means and waits for a yes; --yes answers for a script, which has nobody to ask.
--keys says where each key would come from and asks nothing of anybody, and --dry-run prints
what was found and leaves the settings alone.
Settings → Assistant runs the same survey from a button, about the one model the form is set to. It goes out with the provider, the form and the key as they stand on the settings window rather than with what was last saved, which is how a model, a key or an endpoint can be tried before any of them is kept. What it finds is written over that model's line in the list this command left; filling the list is the command's job, since only it asks about every model.
The CLI answers the [suggest] directive, which is the protocol dotnet-suggest
speaks:
flyback-cli "[suggest:13]" "flyback-cli pr" # print, probe
So installing that tool and adding its shim to your shell gives completion of commands, options and file arguments.
Flyback uses three file types:
.fbk: a patch document.fbkb: a patch bundle containing the patch plus all referenced files.fbks: the patch written as text, in the languageflyback-cli pack nebula.fbk -o nebula.fbkb
A bundle is a zip archive with the patch and any sample/picture files it uses. It is readable by standard tools and does not require unpacking to render.
The app saves and opens all three. A .fbks is a copy rather than a document: it keeps the instrument exactly — the text builds back to the same program, op for op — but not the groups or the canvas layout, so saving one leaves the open document as it was.
flyback-viewer opens a patch and plays it, picture and sound, with no editor around it. It writes nothing — no settings, layout, recovery file or statistics — and takes its defaults from the same output.json the editor saves, every one of them overridable on the command line.
flyback-viewer nebula.fbk
flyback-viewer --preset "No Sense Dub" --size 1080p --mute
flyback-viewer drone.fbk --from 30 --cpu --for 10
flyback-viewer --preset "Whole band" --hidden --for 10
flyback-viewer --help
Hover the top middle of the window for the transport: pause, rewind, the seek bar, loop and sound, with the knobs at the bottom (--transport bottom swaps them); double-click the picture for full screen. A patch with no picture, or a run with --no-video, opens as just the transport and its knobs. A patch that does not say its length has no seek bar or loop, and plays on. Space, or Ctrl+P, pauses. --background opens the window without taking focus, and --hidden opens none at all.
A patch made to be played is played here too: the computer's keys are notes wherever the patch reads them, a MIDI In hears the device it names, and a panel knob bound to a MIDI controller follows it. There is no knob panel, so a knob with no controller stays where the patch left it.
src/Flyback.Web plays a patch in a browser: the engine and the module plugins compiled to WebAssembly, the picture drawn on WebGL 2 by the desktop's own GPU renderer. It opens a shipped preset or a .fbk, .fbkb or .fbks dropped on it, and plays it; nothing else. The sound runs as JavaScript written from the patch, the heaviest showcase presets with a fifth of real time to spare; a patch whose sound still cannot keep up plays its picture alone and says how slow (ADR-0160).
dotnet publish src/Flyback.Web -c Release -p:RunAOTCompilation=true -o artifacts/web
The preset site serves it at /viewer/, and each preset's page has a Play in your browser button that opens it there. On its own, serve artifacts/web/wwwroot from any static server and open /viewer/; ?preset=Nebula, ?file=<url> (with &name= where the URL does not end in the file's name, &title= for the name shown and &back= for the page to return to), ?size=1280x720, ?loop and ?mute pick what opens and how. It offers no presets of its own: the presets page, on the preset site and on GitHub Pages, lists the shipped ones from the build's stills (ADR-0163) and sends one here, and the header leads back to &back=, or to the presets page. Space plays and pauses, left and right seek five seconds, up and down set the volume, M mutes and F goes full screen. A patch's panel knobs are sliders under the picture, and a double-click puts one back. A patch played on the computer keyboard takes its keys as the editor does, as a piano or in the patch's scale, with PageUp and PageDown moving the octave; its note keys win over the shortcuts. The AOT switch needs dotnet workload install wasm-tools; without it the page around the sound runs interpreted, at under half the speed. window.flyback drives the page from a script: presets(), open, play, pause, seek, size(width, height), volume(level), knobs(), turn(knob, value), strike(note, down), release(), status() and still(seconds), the frame as a PNG at the patch's size.
The sound runs without a page under Node, which the workload brings; --knob fog=0 turns a panel knob first, --note 60:0.1:0.6 holds a note on the computer keyboard from one second to another, and --presets lists the presets:
node artifacts/web/hear.mjs --preset "Sidebands" --seconds 2 --out sidebands.f32
src/Flyback.WebEditor is the editor itself in a browser: the same window under Avalonia.Browser, the picture drawn on a canvas of its own by the desktop's GPU renderer (ADR-0162). It keeps nothing between visits, carries every plugin that makes modules, as the viewer does, and plays its sound in the web viewer's worker. The presets page's Edit opens a preset in it, a shipped one by ?preset=<name> and a shared one by ?file=<url> (with &name= and &title= as the viewer takes them). The preset site serves it at /editor/; -p:WebEditor=false builds the site without it:
dotnet run --project src/Flyback.Server
The gallery shows the build's stills from /stills/ where the site has them (ADR-0163), and draws each preset on the page's one thread where it does not; ./stills.sh src/Flyback.Server/wwwroot/stills gives a local run of the site the stills.
window.flyback drives the page from a script: state() says which preset is open, how many modules and wires it has, which renderer draws the picture and at what rate, and the last thing the editor said; preset(name) opens a shipped preset, as /editor/?preset=<name> does on load; openUrl(url, fileName, title) fetches a shared one and opens it, as ?file= does, answering null or why it could not; text() reads the open patch in the language; apply(text) applies text as the text view's Apply does, one edit that one undo takes back, and answers null or what is wrong with it; sound() says how the sound is going.
A patch is a graph, but during rendering it is compiled into a flat straight-line program over registers. Unused sections are not compiled, and the inner loop is designed to be cheap and predictable.
The project is split roughly as:
src/
Flyback.App the editor on the desktop
Flyback.Editor the editor itself: its window, canvas and regions
Flyback.Cli command line tool
Flyback.Core patch model, module API and the built-in modules
Flyback.Engine compiler, text language, renderers and file formats
Flyback.Plugins plugin host and built-in plugin logic
Flyback.Ui the preview, sound device and look the app and the viewer share
Flyback.Viewer the viewer: opens a patch and plays it
Flyback.Web the web viewer: the same, in a browser
Flyback.WebEditor the web editor: the editor in a browser
tests/
Flyback.Core.Tests core engine tests
Flyback.Specs feature requirements as Gherkin scenarios
Flyback.Core.Benchmarks engine benchmarks
Flyback.App.Tests app and UI tests
Flyback.Cli.Tests command line tests
Flyback.Plugins.Tests plugin and runtime behavior tests
Flyback.Plugins.OpenAi.Tests chat-completions session tests
Flyback.Plugins.Gemini.Tests generateContent session tests
docs/engineering-guide.md is how the codebase is put together, how its code is written and how its tests are written. See the docs/adr folder for the architecture decisions behind it.
docs/language.md is the reference for the text language — a
second way to author a patch, decided in
ADR-0065. The app
saves and opens it, the CLI reads it wherever it reads a patch and writes one with print, and the
assistant writes a whole patch in one call with it.
C#
88.3%
GLSL
5.3%
HTML
2.6%
JavaScript
1.3%
Flyback is a patchable synthesiser for .NET 10. One graph can generate both a picture and a sound. The visual path and the audio path share the same module graph and are compiled down to the same flat instruction stream.
See the codeFlyback is a patchable synthesiser for .NET 10. One graph can generate both a picture and a sound. The visual path and the audio path share the same module graph and are compiled down to the same flat instruction stream.
The website has screenshots, tutorials and the plugin guide. See CHANGELOG.md for what changed in each release, and the releases page for downloads.
Flyback is mostly written by a large language model (LLM). A human directs the work, decides what ships and writes some of it by hand. That is how the project happens to be made, not a requirement on anyone who works on it.
The project holds to the same standards any modern codebase would: a CI gate that restores, compiles and runs the whole test suite on every push and pull request, signed releases built from the same image the gate stops inside, architecture decisions recorded in docs/adr/, and a changelog per release. Nothing merges that the gate has not passed.
Tokens are the one thing the project spends money on, and a donation goes on that budget. Flyback is strictly non-profit: nothing in it is sold, nothing is behind a payment, and nobody is paid out of it. The address is in About and in the site's footer.
CONTRIBUTING.md is how to work in it.
dotnet run --project src/Flyback.App -c Release
PATH or picked by hand; Motion JPEG AVI and WAV are written by Flyback itself and need nothingThe solution builds the web editor, which needs the wasm-tools workload:
dotnet workload install wasm-tools
dotnet publish src/Flyback.App -c Release -r win-x64 -o artifacts/win-x64
dotnet publish src/Flyback.Cli -c Release -r win-x64 -o artifacts/win-x64
dotnet publish src/Flyback.Viewer -c Release -r win-x64 -o artifacts/win-x64
This produces a self-contained folder with the app, the CLI and the viewer, plus the shared runtime and plugin folders:
Flyback.exe the app
flyback-cli.exe the command line tool
flyback-viewer.exe the viewer: opens a patch and plays it, and writes nothing
Flyback.Core.dll the patch model and the module API, which plugins are built against
Flyback.Engine.dll the compiler, the language and the renderers
Flyback.Plugins.dll shared plugin host
plugins/ platform backends, and the Picture, Voice, Effects and Mastering modules
Supported publish targets include:
win-x64win-arm64osx-x64osx-arm64linux-x64macOS bundles are published as Flyback.app beside the output folder, whose Info.plist declares .fbk, .fbkb and .fbks. On Windows and Linux, Settings → Files registers the editor or the viewer to open them, for the current user.
docker build --output artifacts .
This restores dependencies, compiles the solution, runs the tests, and publishes the self-contained outputs for the default target set.
To choose a specific set of runtimes:
docker build --build-arg RIDS="win-x64 win-arm64 osx-arm64 osx-x64 linux-x64" --output artifacts .
To restore, compile and run the whole test suite without publishing anything, which is what the Build workflow (.github/workflows/ci.yml) does on every push and pull request:
docker build --target gate .
To measure how much of the code the tests run, into coverage/ with a table per assembly in coverage/summary.md and the specs' figure beside it rather than in the sum, the same way the weekly Coverage workflow does:
./coverage.sh
The Release workflow (.github/workflows/release.yml) builds every platform, zips each one, and publishes them with a SHA256SUMS file and its signature, SHA256SUMS.sig. The app only installs an update when that signature checks out against the public key in src/Flyback.Editor/Updates/release-key.pem. The private key is kept in the repository secret RELEASE_SIGNING_KEY, and the workflow fails before it builds anything if the secret is missing or doesn't match the committed public key. It also fails before building if CHANGELOG.md has no ## X.Y.Z heading for the version being released, since that heading is what the "What's new" window reads. ADR-0088 explains the design. The same key signs the packages of the plugins the preset site starts with and hands out, Figures, Fractals and Easy among them, which no release carries (ADR-0141).
Everything but the publishing is release.sh, which runs the same here:
./release.sh 1.4.0
It builds, tests, packs and signs into dist/, with each platform as a folder to run rather than a zip, and publishes nothing. Off GitHub, RELEASE_SIGNING_KEY holds a local test key rather than the release key: release-key.sh takes it from the environment, or makes one and keeps it in the user environment, and only GitHub stops at a key that does not pair with the committed public key or at a missing changelog heading. The preset site's build and a Release run of the site sign with the same variable, and a Debug build checks no keys at all. A build on this machine, release.sh included, embeds the public half of the local key in place of release-key.pem, so a local release installs over local builds, and its runs count as debug in the usage statistics; on GitHub the committed key is embedded.
To make the key pair, with the openssl that comes with Git Bash:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out flyback-release.key
openssl pkey -in flyback-release.key -pubout -out src/Flyback.Editor/Updates/release-key.pem
Commit the public key. Paste the whole contents of flyback-release.key into the RELEASE_SIGNING_KEY secret, keep a copy somewhere safe such as a password manager, and then delete the file. Never commit it. Installed copies only accept releases signed with the key they were built with, so a lost key means everyone installs the next release by hand.
The CLI runs the same engine without the Avalonia shell, so it is useful for rendering, checking and batch jobs.
flyback-cli render nebula.fbk -o nebula.png --size 1920x1080 --at 2.5
flyback-cli render drone.fbk -o drone.mp4 --seconds 30 --fps 30
flyback-cli render drone.fbk -o drone.avi --seconds 30 --fps 30
flyback-cli render drone.fbk -o drone.mp3 --seconds 30
flyback-cli render drone.fbk -o drone.wav --seconds 30
flyback-cli render drone.fbk -o drone.wav --seconds 30 --loudness
flyback-cli render drone.fbk -o drone.mkv --seconds 30 --format mp4 --ffmpeg /opt/bin/ffmpeg
flyback-cli check nebula.fbk
flyback-cli check nebula.fbk --strict
flyback-cli check --preset "Mycelium" --json
flyback-cli compare nebula.fbk nebula-ported.fbk --seconds 30
flyback-cli info nebula.fbk
flyback-cli info --preset "Plasma"
flyback-cli modules
flyback-cli modules adsr
flyback-cli pack nebula.fbk -o nebula.fbkb
flyback-cli pack --preset "Mycelium" -o mycelium.fbkb
flyback-cli pack-plugin Flyback.Plugins.Ripple.csproj -o ripple.fbkp --key ripple.key
flyback-cli plugin-key -o ripple.key
flyback-cli print nebula.fbk -o nebula.fbks
flyback-cli print nebula.fbk --check
flyback-cli print --preset "Whole band"
flyback-cli render nebula.fbks -o nebula.png
flyback-cli probe --keys
flyback-cli shot --preset "Flyback Theme" -o theme.png --at 30.3 --select "Picture: Scope"
flyback-cli probe --provider all
flyback-cli viewer nebula.fbk
render, check, info, print, pack and shot read a patch file, or a shipped or plugin preset named with --preset in its place; --presets lists the names, as a JSON array under --json, and a name nobody shipped is refused with the list.
render: renders a still, a clip or a sound file from a patch. The extension picks the format — .png, .avi, .mp4, .webm, .mov, .wav, .mp3, .m4a, .flac — and everything but .png, .avi and .wav is encoded by ffmpeg, taken from PATH unless --ffmpeg names one. --format overrides the extension, and --loudness prints how loud the sound came out: integrated loudness in LUFS and true peak in dBTP, measured as ITU-R BS.1770 does. The picture is drawn on the GPU through a headless OpenGL context (EGL on Linux, WGL on Windows) where there is one, and on the processor where there is not; --processor asks for the processor, whose picture is exact to the bit, and --gpu fails rather than fall back. On the processor the patch runs compiled; --interpreted keeps it on the interpreter, which writes the same bytes more slowly.check: compiles the patch and reports issues; for a text patch, --json gives each complaint's line, column and a stable codeinfo: shows module and wire counts and compile cost; --preset describes a shipped preset by name, and --presets lists thempack: packs a patch together with the files it references; --preset packs a shipped preset with the files it carriespack-plugin: builds a plugin into a .fbkp, signed with the key --key namesplugin-key: makes the key a plugin's packages are signed with, which every update must be signed with tooshot: draws the editor's window with the patch open into a PNG, with no screen and no sound: --at is the second the picture is of, run up to from a second and a half before; --size is the window's, 1440x900 unless given; --select selects a box or module by name, so the inspector shows it; --canvas shows the canvas for a patch whose text is the document; --crop writes only the canvas around the modules, with 24 pixels of room. Flyback --shot, beside it, does the drawing (ADR-0166)viewer: starts flyback-viewer with everything after the word, so flyback-cli viewer --help is the viewer's own helpprint: writes the patch out as text in the language, and can check that the text builds back to the same program; --preset prints a shipped preset by name, and --presets lists themcompare: plays two patches side by side for --seconds at --size and says whether they are the same instrument, sample for sample and pixel for pixel, and where they first part when they are not; it exits 1 when they differmodules: lists the modules this build has, and which plugin defines each; given one by type id or name, it describes that module: each socket's default and range, where |> lands, what it carries besides its sockets and what it doesprobe: asks an assistant which models it has and what each one acceptsstills: draws a still of every preset into --out, with the index.json the galleries show them by in place of drawing them (ADR-0163); ./stills.sh <folder> lays out the plugins a build ships first, and every release and site build runs itcheck exits with:
0: no errors1: patch errors2: the job could not run--strict makes a warning fail as well. check, compare, info, pack, modules and
probe each take --json, which writes the same answer as a document instead of
as prose.
info says what a patch requires and modules says what is installed to meet
it, which is the pair to reach for when a patch reports that it did not load
completely.
probe is the one command that reaches off the machine. It asks a provider's endpoint what it
offers and records the answer in the settings file both programs read, so the app's model box
fills itself in without being told. It takes minutes and the provider bills for it, so it says
what that means and waits for a yes; --yes answers for a script, which has nobody to ask.
--keys says where each key would come from and asks nothing of anybody, and --dry-run prints
what was found and leaves the settings alone.
Settings → Assistant runs the same survey from a button, about the one model the form is set to. It goes out with the provider, the form and the key as they stand on the settings window rather than with what was last saved, which is how a model, a key or an endpoint can be tried before any of them is kept. What it finds is written over that model's line in the list this command left; filling the list is the command's job, since only it asks about every model.
The CLI answers the [suggest] directive, which is the protocol dotnet-suggest
speaks:
flyback-cli "[suggest:13]" "flyback-cli pr" # print, probe
So installing that tool and adding its shim to your shell gives completion of commands, options and file arguments.
Flyback uses three file types:
.fbk: a patch document.fbkb: a patch bundle containing the patch plus all referenced files.fbks: the patch written as text, in the languageflyback-cli pack nebula.fbk -o nebula.fbkb
A bundle is a zip archive with the patch and any sample/picture files it uses. It is readable by standard tools and does not require unpacking to render.
The app saves and opens all three. A .fbks is a copy rather than a document: it keeps the instrument exactly — the text builds back to the same program, op for op — but not the groups or the canvas layout, so saving one leaves the open document as it was.
flyback-viewer opens a patch and plays it, picture and sound, with no editor around it. It writes nothing — no settings, layout, recovery file or statistics — and takes its defaults from the same output.json the editor saves, every one of them overridable on the command line.
flyback-viewer nebula.fbk
flyback-viewer --preset "No Sense Dub" --size 1080p --mute
flyback-viewer drone.fbk --from 30 --cpu --for 10
flyback-viewer --preset "Whole band" --hidden --for 10
flyback-viewer --help
Hover the top middle of the window for the transport: pause, rewind, the seek bar, loop and sound, with the knobs at the bottom (--transport bottom swaps them); double-click the picture for full screen. A patch with no picture, or a run with --no-video, opens as just the transport and its knobs. A patch that does not say its length has no seek bar or loop, and plays on. Space, or Ctrl+P, pauses. --background opens the window without taking focus, and --hidden opens none at all.
A patch made to be played is played here too: the computer's keys are notes wherever the patch reads them, a MIDI In hears the device it names, and a panel knob bound to a MIDI controller follows it. There is no knob panel, so a knob with no controller stays where the patch left it.
src/Flyback.Web plays a patch in a browser: the engine and the module plugins compiled to WebAssembly, the picture drawn on WebGL 2 by the desktop's own GPU renderer. It opens a shipped preset or a .fbk, .fbkb or .fbks dropped on it, and plays it; nothing else. The sound runs as JavaScript written from the patch, the heaviest showcase presets with a fifth of real time to spare; a patch whose sound still cannot keep up plays its picture alone and says how slow (ADR-0160).
dotnet publish src/Flyback.Web -c Release -p:RunAOTCompilation=true -o artifacts/web
The preset site serves it at /viewer/, and each preset's page has a Play in your browser button that opens it there. On its own, serve artifacts/web/wwwroot from any static server and open /viewer/; ?preset=Nebula, ?file=<url> (with &name= where the URL does not end in the file's name, &title= for the name shown and &back= for the page to return to), ?size=1280x720, ?loop and ?mute pick what opens and how. It offers no presets of its own: the presets page, on the preset site and on GitHub Pages, lists the shipped ones from the build's stills (ADR-0163) and sends one here, and the header leads back to &back=, or to the presets page. Space plays and pauses, left and right seek five seconds, up and down set the volume, M mutes and F goes full screen. A patch's panel knobs are sliders under the picture, and a double-click puts one back. A patch played on the computer keyboard takes its keys as the editor does, as a piano or in the patch's scale, with PageUp and PageDown moving the octave; its note keys win over the shortcuts. The AOT switch needs dotnet workload install wasm-tools; without it the page around the sound runs interpreted, at under half the speed. window.flyback drives the page from a script: presets(), open, play, pause, seek, size(width, height), volume(level), knobs(), turn(knob, value), strike(note, down), release(), status() and still(seconds), the frame as a PNG at the patch's size.
The sound runs without a page under Node, which the workload brings; --knob fog=0 turns a panel knob first, --note 60:0.1:0.6 holds a note on the computer keyboard from one second to another, and --presets lists the presets:
node artifacts/web/hear.mjs --preset "Sidebands" --seconds 2 --out sidebands.f32
src/Flyback.WebEditor is the editor itself in a browser: the same window under Avalonia.Browser, the picture drawn on a canvas of its own by the desktop's GPU renderer (ADR-0162). It keeps nothing between visits, carries every plugin that makes modules, as the viewer does, and plays its sound in the web viewer's worker. The presets page's Edit opens a preset in it, a shipped one by ?preset=<name> and a shared one by ?file=<url> (with &name= and &title= as the viewer takes them). The preset site serves it at /editor/; -p:WebEditor=false builds the site without it:
dotnet run --project src/Flyback.Server
The gallery shows the build's stills from /stills/ where the site has them (ADR-0163), and draws each preset on the page's one thread where it does not; ./stills.sh src/Flyback.Server/wwwroot/stills gives a local run of the site the stills.
window.flyback drives the page from a script: state() says which preset is open, how many modules and wires it has, which renderer draws the picture and at what rate, and the last thing the editor said; preset(name) opens a shipped preset, as /editor/?preset=<name> does on load; openUrl(url, fileName, title) fetches a shared one and opens it, as ?file= does, answering null or why it could not; text() reads the open patch in the language; apply(text) applies text as the text view's Apply does, one edit that one undo takes back, and answers null or what is wrong with it; sound() says how the sound is going.
A patch is a graph, but during rendering it is compiled into a flat straight-line program over registers. Unused sections are not compiled, and the inner loop is designed to be cheap and predictable.
The project is split roughly as:
src/
Flyback.App the editor on the desktop
Flyback.Editor the editor itself: its window, canvas and regions
Flyback.Cli command line tool
Flyback.Core patch model, module API and the built-in modules
Flyback.Engine compiler, text language, renderers and file formats
Flyback.Plugins plugin host and built-in plugin logic
Flyback.Ui the preview, sound device and look the app and the viewer share
Flyback.Viewer the viewer: opens a patch and plays it
Flyback.Web the web viewer: the same, in a browser
Flyback.WebEditor the web editor: the editor in a browser
tests/
Flyback.Core.Tests core engine tests
Flyback.Specs feature requirements as Gherkin scenarios
Flyback.Core.Benchmarks engine benchmarks
Flyback.App.Tests app and UI tests
Flyback.Cli.Tests command line tests
Flyback.Plugins.Tests plugin and runtime behavior tests
Flyback.Plugins.OpenAi.Tests chat-completions session tests
Flyback.Plugins.Gemini.Tests generateContent session tests
docs/engineering-guide.md is how the codebase is put together, how its code is written and how its tests are written. See the docs/adr folder for the architecture decisions behind it.
docs/language.md is the reference for the text language — a
second way to author a patch, decided in
ADR-0065. The app
saves and opens it, the CLI reads it wherever it reads a patch and writes one with print, and the
assistant writes a whole patch in one call with it.
C#
88.3%
GLSL
5.3%
HTML
2.6%
JavaScript
1.3%