Crates.io | murrelet |
lib.rs | murrelet |
version | 0.1.1 |
source | src |
created_at | 2024-09-29 05:18:07.231039 |
updated_at | 2024-10-24 16:25:03.285934 |
description | Murrelet the live coding framework for visuals |
homepage | |
repository | https://github.com/jessstringham/murrelet.git |
max_upload_size | |
id | 1390605 |
size | 10,641 |
Along with this repo, this README is a work in progress!
The crates here are part of the livecode engine that I've been building and using to make nearly all the art as this.xor.that.
A demo of this (should be) running here. The code for creating the WASM for the website is in examples/foolish-guillemot
, and the main.js is here.
A high-level overview of the software is published here.
I wanted to open source my code so I could share some ideas of how I've been implementing my livecode software. So that means:
These libraries are in initial development and the API is not stable.
At this time, I'm not sure if I'll accept PRs on this repo. If there is interest, I might entertain spinning off a more manageable chunk of the code to maintain and document and all that.
I'm still learning Rust and computer graphics, so there will be funny weird things.
This repo can be broken down into a few parts:
livecode macros and code: how I turn Rust sketches into YAML-controlled live performance.
murrelet_gpu: some cute little macros for managing and chaining shaders. (this is not live at the moment)
murrelet_svg, murrelet_draw: drawing logic. tbh, mostly included out of necessity for the demo.
The two main ones here are livecode and unitcells, but there's a few others.
The Livecode macros makes it possible to control parameters of a struct by injecting some info about the world (time, audio, midi, etc), combined with expressions.
Unitcells can be used to dynamically create a list of things. The number of things and the arrangement (grid, symmetry) is controlled by sequencers (see murrelet_draw/sequencers for examples).
This is.. not working. But I haven't wanted to delete the code yet nor have had a reason to get it to work, so it's broken for now. I do want to fix it or reimagine eventually!
Boop is a funny not-quite-working bit of code that's meant to help interpolate values and avoid hard jumps when you update a value.
The one implemented right now are ODEs, which let you to use some features from animation, like anticipation (going a little in the opposite direction before going in the intended direction).
This is a way to access/update a value in a nested struct using a string.
I made this to explore the parameter space of something like wallpaper groups (which involve enums and strings). So I can have one piece that lists out different configurations.
There are a few macros here for building shaders.
The build_shader
just hides some boilerplate of the fragment shader.
let gradient_def: String = build_shader! {
(
raw r###"
let start: vec4<f32> = uniforms.more_info;
let end: vec4<f32> = uniforms.more_info_other;
let result = mix(start, end, tex_coords.x);
"###;
)
};
let gradient_red = prebuilt_graphics::new_shader_basic(c, "grad", &gradient_def);
gradient_red
.update_uniforms_other_tuple(c, ([0.0, 0.0, 1.0, 0.04], [1.0, 0.0, 1.0, 0.04]));
and then build_shader_pipeline
let's you take those graphics and
write to input textures of others.
(for extra fun, I use Fira font with arrow glyphs)
let example_pipeline = build_shader_pipeline! {
gradient_red -> drawing_placeholder;
drawing_placeholder -> DISPLAY;
};
this is a work in progress and is probably pretty sloppy with programming language terms sorry
Some interesting variables are injected in different scopes, making them available in different fields.
In a basic example, you need to know about just two scopes:
world: the context per frame. includes things like time, midi, audio, global functions, and the app > ctx field. You can use these variables in every field (except the time config).
unitcell: the context per unitcell, which includes information like the x and y location and a unique seed for each instance.
That's an oversimplification. Here's roughly how the scopes should work:
These three do strictly build on top of each other
LiveCodeUtil
, so you probably won't run into it.t
-based variable. Basically exclusively used to load the AppConfigTiming
config.Once you're within a world, going deeper can get as complicated as you want using a combination of:
For example, you might set up a sketch where a unitcell sequencer might contain a second unitcell sequencer (using a different variable prefix) that draws things that combine the outer and inner unitcell's variables.
Right now this is built on top of evalexpr
. By default, it has support for inputs using:
I also included packages of how I add platform-specific implementations (this is what I use on the native build, i.e. not the web)
To see how exactly the variables are defined, you generally want to look for the IsLivecodeSrc
trait implementation.
The float variable t
represents time in expressions. This is very useful for making things bounce and change to a bpm for live performances. I also use it to explore parameter spaces, like setting a field to s(ease(t, 0.25), 1.0, 20.0)
to ease between 1.0 and 20.0.
The value of t
is an abstraction that should increment by 1.0
every bar, given the definitions in the fields of AppConfigTiming
, which might look something like this:
app:
...
time:
realtime: true
fps: 60.0
bpm: 135.0
beats_per_bar: 4.0 # defaults to 4.0
For live performances, this should probably be set to true
so you can match the bpm
of the music, regardless of if the visuals start rendering faster or slower. For recording a video, you might want realtime
to be false
to avoid jumps.
Aside: For generative art, I sometimes switch between them: the glitchiness based on how fast my machine is rendering can make nice textures of realtime: true
, but other times I want the even spacing of realtime: false
.
If realtime: true
, it'll use bpm and beats_per_bar and the system's clock to figure out what t
should be. If realtime: false
, instead of the system time, it'll use the current frame number to compute t
.