"you africans, please listen to me as africans
and you non-africans, listen to me with open mind"
- read this readme
- there's some exapmles of the basic ideas in doc
- drag and drop examples from the assets folder into a quartz window
- if anything is confusing, please start a discussion: https://github.com/tomara-x/quartz/discussions
to build from source:
- install rust: https://www.rust-lang.org/tools/install
- install bevy dependencies: https://bevyengine.org/learn/quick-start/getting-started/setup/#installing-os-dependencies
- on linux also install libjack-dev
- clone quartz
git clone https://github.com/tomara-x/quartz.git
- build it
cd quartz
cargo run --release
development happens on the main
branch
alternatively you can download stable releases from: https://github.com/tomara-x/quartz/releases
there's an experimental wasm build here: https://tomara-x.github.io/quartz/ (no audio input, no file loading/saving, and no OSC)
wasm building
uncomment the commented out dependencies in cargo.toml, then:
RUSTFLAGS=--cfg=web_sys_unstable_apis cargo build --profile wasm-release --target wasm32-unknown-unknown
wasm-bindgen --out-name quartz --out-dir target --target web target/wasm32-unknown-unknown/wasm-release/quartz.wasm
more details: https://github.com/bevyengine/bevy/tree/main/examples#wasm
when you open quartz, it will be an empty window. there's 3 modes:
- edit: (default) interact with entities and execute commands (press
e
oresc
) - draw: draw new circles (press
d
) - connect: connect circles (press
c
)- target: target an entity from another (hold
t
in connect mode)
- target: target an entity from another (hold
hold space
, then drag to pan the view, or scroll to zoom in and out
terms:
- circle1: an object that you create in draw mode (they're regular polygons)
- hole: a connection object. these always come in (black hole - white hole) pairs
- entity: i'll use that to refer to any object (circle or hole)
note: in this file i'll use square brackets []
in arguments to denote an optional argument
both circles and holes have:
- position: x, y, z (z controls depth, what's in front of what)
- color: in hsla (hue, saturation, lightness, alpha) (hue is in the range [0...360], the rest in the range [0...1])
- radius: from zero to beeeg
- vertices: the number of sides (3 or more)
- rotation: in radians [-PI...PI]
a circle has other things in addition:
- a number (32-bit float)
- an op string: defining what that circle does
- for example:
sum
,sine()
,toggle
,screenshot
,lowpass()
- for example:
- an array of numbers: for different uses (empty by default)
- an array of target entities that this circle controls in some way (empty by default too)
- an order number: defining if/when that circle is processed
- an audio node (defined by the op string) (empty by default)
holes store information about what they connect:
- a white hole has:
- id of the black hole it's connected to
- id of the circle that has that black hole
- link types
- an open status (whether or not to read data from the black hole2)
- a black hole has:
- id of the white hole it's connected to
- id of the circle that has that white hole
there are 2 types of commands:
- return-terminated
(type it then press enter) (you can separate them with
;
to run more than one at once)
scene saving/loading
:e {file name}
edit (open) a scene file (in the assets path) (no spaces):w {file name}
write (save) a scene file (same)
:w moth.cute // saves the current scene as the file "assets/moth.cute" (OVERWRITES)
:e soup.fun // opens the file "assets/soup.fun" if it's there
(dragging and dropping scene files into a window also works)
set values
:set n [id] {float}
set num value:set r [id] {float}
set radius (use rx or ry to set those independently):set x [id] {float}
set x position:set y [id] {float}
set y position:set z [id] {float}
set z position (this controls depth. what's in front of what):set h [id] {float}
set hue value [0...360]:set s [id] {float}
set saturation [0...1]:set l [id] {float}
set lightness [0...1]:set a [id] {float}
set alpha [0...1]:set v [id] {float}
set number of vertices (3 or higher):set o [id] {float}
set rotation [-pi...pi] (:set rot
and:set rotation
also work):set op [id] {string}
set the op string (use shortcuto
):set ord[er] [id] {float}
set order (use]
and[
to go up/down by one):set arr[ay] [id] {float float ...}
set the array (space separated):set tar[gets] {id id ...}
set targets (if nothing is selected, the first entity gets the rest of the list as its targets):tsel {id}
target selected (:tsel 4v2
sets selected entities as targets of entity 4v2):push {float}/{id}
push a number to the array, or an id to the targets array:lt [id] {link type}
set holes' link type (use shortcutl
)
:set n 4v0 42 // will set the num of entity 4v0 to 42
:set n 42 // will set the num values of selected entities to 42
audio device selection
:od {index} {index} [sample rate] [buffer size]
set the output audio device. first index is the host, second is the device index (use the commandsah
andao
to get those) if sample rate and buffer size aren't given, the device defaults will be used:id {index} {index} [sample rate] [buffer size]
set the input audio device
notes:
- if device has a sampling rate different from 44100, you need to connect your node to an
sr()
node (with matching sr) before connecting that toout()
(unless you want mismatched sr) - after changing input device, you must re-set the op of the
in()
node. (because it only initializes internal stuff once the op is set and it depends on the selected device) - after changing output device, you must open the white hole connecting your node to the
out()
node (the new device will not automatically read it)
other
:nl
set the maximum number of nodes a connective op (+
,*
,>>
, etc) will allow (default 500) (saved in scene file):reset_bloom
if you change bloom settings to the point where you can't see what's happening, reset them:reset_cam
in case you took it too far with thecam
op (might need to set everything to order 0 first):dv {float}
set default number of vertices of drawn circles:dc {float} [float] [float] float]
set default color of drawn circles (h s l a):ht {id}
toggle open a white hole (by id):q
exit (don't combine with other commands using;
)
note: the std constants, inf
, -inf
, nan
are valid floats. e.g. :set op dc(-PI)
, :set n TAU
, :set x inf
- immediate commands (these execute when you finish typing them)
drag modes
(what happens when dragging selected entities, or when arrow keys are pressed)
- exclusive:
ee
drag nothing (default)et
drag translation (move entity)er
drag radiusen
drag numbereh
drag huees
drag saturationel
drag lightnessea
drag alphaeo
drag rotationev
drag vertices
- add a drag mode: (to drag multiple properties at the same time)
Et
add translationEr
add radiusEn
add numEh
add hueEs
add saturationEl
add lightnessEa
add alphaEo
add rotationEv
add vertices
shortcuts
o
shortcut for:set op
l
shortcut for:lt
inspect commands
(information about the selected entities)
ii
entity idsin
number valuesira
radius valuesix
x positioniy
y positioniz
z positionihu
hue valueis
saturationil
lightnessial
alphaiv
verticesiro
rotationior
orderiop
opiar
arrayiho
holesit
targetsiL
hole link typeiO
white hole open status
audio node info
ni
number of inputsno
number of outputsnp
info about the node
audio hosts/devices
ah
list available audio hostsao
list output devicesai
list input devices
selection
sa
select allsA
deselect allsc
select all circlessC
deselect circlessh
select all holessH
deselect holessv
select visible entities (in view)sV
deselect visible entitiessg
select holes of the selected circlesst
select targets of the selected circles<delete>
delete selected entitiesyy
copy selection to clipboardp
paste copied
notes:
- holding shift, then clicking an entity, or dragging across and area will add to the selection
- when drag-selecting, holding
alt
will only select circles (ignores holes), holdingctrl
will only select holes (ignores circles) - ctrl+clicking a selected entity will deselect it
visibility
vc
toggle circle visibilityvb
toggle black hole visibilityvw
toggle white hole visibilityva
toggle arrow visibilityvt
toggle info textsvT
toggle id in info texts (flickvt
after changing this)vv
show all
other
<F11>
toggle fullscreenht
toggle white hole open statusF
freeze the command line (pressesc
,Enter
, orBackspace
to reactivate it)quartz
shhh!version
print versionawa
awawawa
any connection links 2 circles together in some way. the black hole is taking some data from the source circle, and the white hole is getting that data and feeding it to the sink circle. the link type determines what that data is.
n
or-1
: numr
or-2
: radiusx
or-3
: x positiony
or-4
: y positionz
or-5
: z positionh
or-6
: hues
or-7
: saturationl
or-8
: lightnessa
or-9
: alpha-10
: order (only withdistro
)v
or-11
: verticeso
or-12
: rotationA
or-13
: arrayT
or-14
: targets- positive numbers: used to denote "input number x"
0
can mean audio node, op string, or nothing (it depends on the op) a0 -> 0
connection does nothing (except for connective ops, seq, and select)
use }
/{
to go up/down by one. or use l
with selected holes to set a specific link type
every circle has an order (0 or higher). things in order 0 do nothing.
each frame, every circle with a positive order gets processed (does whatever its op defines) this processing happens.. you guessed it, in order
we process things breadth-first. so when a circle is processed, all its inputs must have their data ready (they processed in this frame) to make sure that's the case, their order has to be lower than that of the circle reading their data...
lower order processes first, and the higher the order, the later that circle processes (within the same frame)
unless...
a circle has an array of "targets" those are just entity ids. so it's like a "pointer" to another circles or hole. think of it as a one-way wireless connection.
some ops make a circle do things to its targets. like process
, del_targets
, spin_target
, distro
, reorder
, (see ops for full list)
(they allow some things that aren't easy through normal processing. since circles read their input when they process, while targets are written to when the controller circle is processed instead)
targets
process
- this circle will process its targets in the order they appear in the targets array. it doesn't matter what order those targets are. even if they're at order 0 (it's preferable they are at 0 so you don't cause unexpected things). so for every frame a circle with a
process
op is processed, it processes all of its targets in order. - you can't nest them. so if a process has another process in its targets, that won't process the second one (to avoid blowing up computers)
- this circle will process its targets in the order they appear in the targets array. it doesn't matter what order those targets are. even if they're at order 0 (it's preferable they are at 0 so you don't cause unexpected things). so for every frame a circle with a
select_target
- input:
n -> 1
- select the targets when input is non-zero, deselect them when it's zero
- input:
open_target
- inputs:
n -> 1
- open target white holes when input is non-zero
- inputs:
close_target
- inputs:
n -> 1
- close target white holes when input is non-zero
- inputs:
open_nth
- inputs:
n -> 1
- open nth target once if it's a white hole
- inputs:
del_target
- inputs:
n -> 1
- delete targets and clear targets array when input is non-zero
- inputs:
spin_target
- inputs:
n
,n -> 1
- rotate targets around this circle by
n
(radians) when input is non-zero
- inputs:
reorder
- inputs:
n -> 1
- set target circles' order to input
n
- inputs:
spawn
- inputs:
n -> 1
- spawn a new circle similar to this one when input is non-zero. the new circle is added to this circle's targets. only the color, vertices, and transform (ish) are copied (z depth is increased with each one)
- inputs:
distro
- inputs:
A -> n/r/x/y/z/r/o/v/h/s/l/a/-10
(any number of those) - distribute values from input array among targets
- inputs:
connect_target
- inputs:
n -> 1
, [T -> 2
] - remove holes from targets array, then connect each target circle to the next. if array contains 2 numbers they will be used as the connection type (otherwise
0 -> 0
) if second input is provided, the white holes created will be added as targets to that circle
- inputs:
isolate_target
- inputs:
n -> 1
- delete all connections target has when input is non-zero
- inputs:
target_lt
- inputs:
n -> 1
- for hole targets, set their link type to input num
- inputs:
repeat
- inputs:
n
,T -> 1
- repeat input targets array n times
- inputs:
arrays
zip
- inputs:
A -> 1
,A -> 2
- zip array 1 and array 2
- inputs:
unzip
- inputs:
A -> 1
- unzip input array (one side remains in input array, the other side is in this circle)
- inputs:
push
- inputs:
n -> 1
- push input num to this circle's array
- inputs:
pop
- inputs:
n -> 1
- pop the last number in the array and set this circle's num to it when input is non-zero
- inputs:
len
- inputs:
A -> 1
orT -> 1
- length of array (or targets array)
- inputs:
append
- inputs:
A -> 1
- copy input array and append it to the end of this circle's array
- inputs:
slice
- inputs:
n
,A -> 1
- slice input array at index
n
, [0..n] remain in input array, [n..len] are moved to this circle's array
- inputs:
resize
- inputs:
n -> 1
- resize this circle's array, discards numbers when shrinking, and adds zeros when growing
- inputs:
contains
- inputs:
A -> 1
,n -> 2
- outputs 1 when input array contains input num, 0 otherwise
- inputs:
set
- inputs:
n -> 1
,n -> 2
- first input is index, second is value. sets the value of the given index of this circle's array
- inputs:
get
- inputs:
A -> 1
,n -> 2
- get the value at index of the input array
- inputs:
collect
- inputs:
n -> {non-negative}
(any number of those) - collect all connected nums and add them in order to this circle's array
- inputs:
settings
clear_color
- when color changes (drag h/s/l), sets the background color (the clear color)
draw_verts
- when vertices change, set the default drawing vertices for future circles
draw_color
- when color changes, set the default drawing color
highlight_color
- when color changes, set the highlight color (the outline around selected entities)
indicator_color
- when color changes, set the color of the selecting/drawing/connecting indicator
connection_color
- when color changes, set the color of connection arrows
connection_width
- when this circle's num changes, set the width of the connection arrows
command_color
- when color changes, set color of the command line text
text_size
- when this circle's num changes, set the font size of info texts
tonemapping
- inputs:
n -> 1
- input num sets the tonemapping mode. 0 =
None
, 1 =Reinhard
, 2 =ReinhardLuminance
, 3 =AcesFitted
, 4 =AgX
, 5 =SomewhatBoringDisplayTransform
, 6 =TonyMcMapface
, 7 =BlenderFilmic
(default: 6 tony)
- inputs:
bloom
- control bloom parameters
- inputs:
n -> 1
: intensity (default: 0.2)n -> 2
: low frequency boost (default: 0.6)n -> 3
: low frequency boost curvature (default: 0.4)n -> 4
: high pass frequency (default: 1)n -> 5
: composite mode (if n > 0Additive
elseEnergyConserving
) (default: additive)n -> 6
: prefilter threshold (default: 0)n -> 7
: prefilter threshold softness (default: 0)
all of these except for the tonemapping and bloom settings are saved inside the scene file (so deleting the circle after changing that setting is fine) but for persistent change to bloom/tonemapping you have to leave the circles with the input values attached to them in the scene
utils
cam
n -> 1
camera x positionn -> 2
camera y positionn -> 3
camera z position (can be useful if you're playing with extremes in depth)n -> 4
camera rotationn -> 5
zoom
update_rate
- inputs:
n -> 1
,n -> 2
- by default quartz will respond (as fast as possible) to any mouse input/movement, or keyboard input, or if the refresh duration has elapsed. that duration is by default 1/60 of a second (60fps) when the window is in focus, and 30fps when out of focus. first input is the refresh rate (in hz) for focused mode, second input is unfocused rate
- inputs:
command
- inputs:
0 -> 1
(op string to first input) - when the white hole is open set the command line text to the string of the input circle
- inputs:
screenshot
- inputs:
n -> 1
- when input num is non-zero, take a screenshot and save it as screenshots/{time in ms since 1970}.png (make sure that folder exists)
- inputs:
osc
- set the settings of osc sender and receiver
n -> 1
receiver port (needs to be specified for receiving to work)0 -> 2
op string of the input sets the host ip (ip to send to) (defaults to 127.0.0.1 (localhost))n -> 3
sender port (defaults to 1729)
osc_r_{osc address}
- receive osc messages into the array of this circle. the
osc
op must be present in this patch and is processing for this to work. the osc messages must be sent to the given osc address and contain floats. you can receive from multiple addresses - e.g.
osc_r /gyroscope
,osc_r /touch1 /touch3
- receive osc messages into the array of this circle. the
osc_s_{osc address}
- inputs:
A -> 1
- send the input array as an osc message with the given address (to the host and port set by the
osc
op) - e.g.
osc_s /space
- inputs:
for more info about osc: https://opensoundcontrol.stanford.edu/spec-1_0.html
input
mouse
- array stores mouse position (in world coordinates) [x, y]
lmb_pressed
- num = 1 if left mouse button is pressed, 0 otherwise
mmb_pressed
- num = 1 if middle mouse button is pressed, 0 otherwise
rmb_pressed
- num = 1 if right mouse button is pressed, 0 otherwise
butt
- num = 1 when clicked, 0 otherwise
toggle
- num = 1 when clicked, 0 when clicked again (kinda)
key
- pressed keyboard keys are added to this circle's array and removed when released. for keys corresponding to an ascii character that's their decimal ascii code, for other keys it's an arbitrary convention that i put together in 5 minutes:
Control
: 128,Shift
: 129,Alt
: 130,Super
: 131,Fn
: 132CapsLock
: 133,NumLock
: 134,ScrollLock
: 135End
: 136,Home
: 137,PageUp
: 138,PageDown
: 139Insert
: 140,ContextMenu
: 141ArrowUp
: 200,ArrowDown
: 201,ArrowLeft
: 202,ArrowRight
: 203F1
: -1,F2
: -2 ..F12
: -12
- pressed keyboard keys are added to this circle's array and removed when released. for keys corresponding to an ascii character that's their decimal ascii code, for other keys it's an arbitrary convention that i put together in 5 minutes:
pressed_{one or more characters}
- e.g.
pressed Hi
this circle's num will be set to 1 when eitherH
ori
is pressed, zero otherwise
- e.g.
data management xD
apply
- inputs:
0 -> 1
(input audio node),A -> 2
(input array) - process the input array as input to the given audio node (array length must match the number of input channels the node has) output of the node is written to this circle's array (process one audio frame)
- inputs:
render
- inputs:
n
,0 -> 1
(input node),n -> 2
(trigger) - render n samples from the given audio node into the array when the second input is non-zero (node must have 0 ins, and 1 out). can process a maximum of 10 million samples at a time (a limit to avoid causing memory issues and excessive cpu usage)
- inputs:
store
- inputs:
n -> 1
- store the input num into this circle's num, but doesn't open the white holes reading nums like usual
- inputs:
num_push
- inputs:
n -> 1
- output this circle's num (open all white holes reading it) when the input num in non-zero
- inputs:
worm[string]
- multiple circles with the same op string (starting with
worm
) will mirror the same number value. when one's num changes, the others follow. their order matters, as value flows from lower to higher order
- multiple circles with the same op string (starting with
sum
- inputs:
n -> 1
(any number of those) - convenience op for adding numbers together
- inputs:
product
- inputs:
n -> 1
(any number of those) - multiply numbers together
- inputs:
audio node management
refer to the fundsp readme, and docs for more details (sometimes)
+
orSUM
- inputs:
0 -> {non-negative}
(any number of those),n
(repetitions) - sum given nodes together. their number of outputs must match, their inputs are stacked together in the order they appear in connections
- inputs:
*
orPRO
- inputs:
0 -> {non-negative}
(any number of those),n
(repetitions) - multiply given nodes together. their number of outputs must match, their inputs are stacked together in the order they appear in connections
- inputs:
-
orSUB
- inputs:
0 -> 1
,0 -> 2
- node 1 - node 2 (number of outputs of those nodes must match)
- inputs:
>>
orPIP
- inputs:
0 -> {non-negative}
(any number of those),n
(repetitions) - pipe nodes though each other. if outputs of node 1 matches inputs of node 2 they're piped together, and so on
- inputs:
|
orSTA
- inputs:
0 -> {non-negative}
(any number of those),n
(repetitions) - stack inputs and outputs of given nodes
- inputs:
&
orBUS
- inputs:
0 -> {non-negative}
(any number of those),n
(repetitions) - bus given nodes together. number of inputs and outputs must match. input is passed through each node and output from them is mixed at output
- inputs:
^
orBRA
- inputs:
0 -> {non-negative}
(any number of those),n
(repetitions) - branch given nodes together (same inputs are passed to each node, but their outputs are kept separate)
- inputs:
!
orTHR
- inputs:
0 -> 1
- pass extra inputs through
- inputs:
branch()
- inputs:
A -> 1
,0 -> 2
- create as many nodes as the input array has values, replacing the "#" in the second input's op with each value, all branched together. e.g. array: [1, 2, 3] and op string "lowpass(1729, #)" creates the node
lowpass(1729, 1) ^ lowpass(1729, 2) ^ lowpass(1729, 3)
- inputs:
bus()
- same as branch() but bus nodes together instead
pipe()
- same as branch() but pipe nodes together
stack()
- same as branch() but stack nodes
sum()
- same as branch() but sum
product()
- same as branch() but
swap()
- inputs:
0 -> 1
- swap the node without resetting the graph if arity is identical. connecting a different arity will reset the graph
- inputs:
out()
ordac()
- inputs:
0 -> 1
- output given node to speakers (node must have 1 or 2 outputs)
- inputs:
in()
oradc()
- node with 2 outputs corresponding to the quartz input device (mic input and the like)
var()
- node: 0 ins, 1 out
- create a shared variable audio node. its output is the value of this circle's num. must have an order >= 1
monitor()
- node: 1 in, 1 out (it passes audio through)
- create a monitor node. sets the value of this circle's num to the latest sample that passed through this node. must have an order >= 1
timer()
- when stacked with another node, this will maintain the current time of that node in this circle's number. must have an order >= 1
buffin()
andbuffout()
- these 2 are useful when you need to get samples from a point in the audio graph but not use them in audio (like when you want to use
render
to visualize audio). this is a channel with a buffer size (determined by the number of the buffin() circle. then this circle will have an audio node that passes audio through but sends it to the buffout(). connect them like this to link them:buffin() 0 -> 1 bufout()
then whenever the audio node of buffout() processes (using apply or render) it will receive the samples sent from the audio graph. buffer can have a capacity of 1..100000. these nodes are basically likemonitor()
, but buffered instead of receiving only the latest sample. (see theassets/scope
andassets/circular-scope
scenes for examples)
- these 2 are useful when you need to get samples from a point in the audio graph but not use them in audio (like when you want to use
get()
- node: 1 in (index), 1 out (value)
- copies this circle's array into node so it can be indexed at audio-rate. input is index, output is the value at that index
quantize()
- inputs:
A -> 1
(array of steps to quantize to. must have at least 2 different values) - node: 1 in, 1 out
- quantize input to the nearest value in the given steps
- inputs:
feedback()
- inputs:
0 -> 1
(input node), [n -> 2
] (optional delay) - mixes outputs of given node back into its inputs (number of node ins/outs must match)
- node: ins and outs are the same as the input node
- inputs:
kr()
- inputs:
n
,0 -> 1
(input node) - node: ins and outs are the same as the given node
- process the input node once every n samples (this can be used to optimize things that don't need to be processed sample-by-sample)
- inputs:
s()
- same as
kr()
but sincekr()
will process the node less frequently than it would normally, that has an effect on how it behaves (times and frequencies get stretched)s()
avoids that by setting the sr of the given node to sr/n in order to preserve the time of that node
- same as
sr()
- inputs:
n
,0 -> 1
(input node) - set the sample rate for the input node
- inputs:
reset()
- inputs:
n
,0 -> 1
(input node (must have 0 ins, and 1 out)) - node: 0 ins, 1 out
- process the input node, but reset it every n seconds (rounded to nearest sample)
- inputs:
reset_v()
- inputs:
0 -> 1
(input node (must have 0 ins, and 1 out)) - node: 1 in, 1 out
- process the input node but reset it every n seconds. n is specified by the input to this node
- inputs:
trig_reset()
- inputs:
0 -> 1
(input node (must have 0 ins, and 1 out)) - node: 1 in, 1 out
- reset the given node whenever the input is non-zero
- inputs:
seq()
- inputs:
0 -> {non-negative}
(any number of those) - node: 4 ins (trig, node index, delay, duration), 1 out (output from sequenced nodes)
- sequences the given nodes and mixes their outputs at output (valid input nodes must have no inputs, and only one output). for every sample trig is non-zero, add an event for the node at index with the given delay and duration (in seconds, rounded to nearest sample)
- indexes are collected. e.g. if circle has three connections:
0 -> 1
0 -> 5
0 -> 8
this is gonna be a sequencer node that accepts indexes 0, 1, and 2. the node at 1 has index 0, node at 5 has index 1, and node at 8 has index 2. and only valid nodes are added.
- inputs:
select()
- inputs:
0 -> {non-negative}
(any number of those) - node: 1 in (index of selected node), 1 out (output from that node)
- create a node that switches between input nodes based on index
- inputs:
wave()
- inputs:
A -> 1
- node: 0 ins, 1 out
- create a wave player from the input array
- inputs:
audio nodes
refer to the fundsp readme, and docs for more details (in some cases)
sources
sine([float])
e.g.sine(440)
has no inputs, and outputs a sine wave at 440Hz.sine()
takes 1 input (frequency) and outputs sine wavesaw([float])
(same)square([float])
(same)triangle([float])
(same)organ([float])
(same)hammond([float])
(same)soft_saw([float])
(same)dsf_saw([float])
dsf_saw()
takes 2 inputs (frequency, and roughness [0...1]),dsf_saw(0.5)
takes only a freq input.dsf_square([float])
(same)pulse()
pulse wave oscillator (frequency, and duty cycle [0...1])brown()
brown noisepink()
pink noisewhite()
noise()
white noisezero()
silenceimpulse()
one sample impulselorenz()
rossler()
constant(float)
dc(float)
pluck(float, float, float)
(frequency, gain per sec, high freq damping) input is string excitation signalmls([float])
ramp()
ramp from 0 to 1 at input freq (phasor)
filters
allpole([float])
delay in samples. if not provided the node takes 2 inputs (signal, delay)pinkpass()
allpass([float], [float])
if 1 param is given, that's the q, and the node takes 2 input channels (signal, and hz) if 2 are given, that's the hz and q and the node only takes input signalbandpass([float], [float])
(same as allpass)bandrez([float], [float])
(same)bell([float, float], [float])
if 2 params are given, they're (q, gain), if 3 are give, they're (hz, q, gain), if none, the node takes 4 channels (input, hz, q, gain)biquad(float, float, float, float, float)
butterpass([float])
e.g.butterpass()
takes 2 inputs (signal, and hz).butterpass(1729)
takes 1 inputdcblock([float])
if no param is provided, the cutoff is 10Hzfir(float [float], [float], ...)
(up to 10 weights)fir3(float)
param is gain at nyquistfollow(float, [float])
attack and release response timeshighpass([float], [float])
(same as allpass)highpole([float])
(same as butterpass)highshelf([float, float], [float])
(same as bell)lowpass([float], [float])
(same as allpass)lowpole([float])
(same as butterpass)lowrez([float], [float])
(same as allpass)lowshelf([float, float], [float])
(same as bell)moog([float], [float])
(same as allpass)morph([float, float, float])
(hz, q, morph [-1...1] (-1 = lowpass, 0 = peak, 1 = highpass)) if not provided, the node takes 4 inputs (signal, hz, q, morph)notch([float], [float])
(same as allpass)peak([float], [float])
(same)resonator([float, float])
(hz, bandwidth) if not provided the node takes 3 inputs (signal, hz, bandwidth)
channels
sink()
eats an input channelpass()
takes an input channel and passes it unchangedchan(float, float,...)
shortcut for stacking pass/sink nodes. e.g.chan(0,1,0,1,2)
is the same assink() | pass() | sink() | pass() | pass()
(non-zero is pass)pan([float])
e.g.pan(0)
pan input (mono to stereo)pan()
takes 2 inputs (signal, and pan [-1...1])join(float)
float can be [2...8] e.g.join(8)
takes 8 inputs and averages them into 1 outputsplit(float)
float can be [2...8]split(8)
takes 1 input and copies it into 8 outputsreverse(float)
float can be [2...8] reverse the order of channels
envelopes (all subsampled at ~2 ms)
adsr(float, float, float, float)
xd([float])
(this is just anexp(-t*input)
)xD([float], [float])
e.g.xD()
takes 2 inputs (time and curvature)xD(5)
takes 1 input specifying the decay time with a curvature of 5.xD(10, 0.5)
is a decay over 10 seconds with a curvature of 0.5.ar([float, float], [float, float])
if there are no params it takes 4 inputs, if there are 2 params they are the curvature of attack and release and the node takes 2 inputs specifying the times, if there are 4 params they are (attack time, attack curvature, release time, release curvature)
other
tick()
one sample delayshift_reg()
2 ins (input signal, trigger signal), 8 outs (outputs of the shift register)snh()
sample and hold node. 2 ins (input signal, trigger signal), 1 out (held signal)meter(peak/rms, float)
e.g.meter(rms, 0.5)
rms(peak, 2)
chorus(float, float, float, float)
(seed, separation, variation, mod frequency)declick([float])
e.g.declick()
10ms fade in,declick(2)
2 second fade indelay(float)
e.g.delay(2)
2 second delayhold(float, [float])
e.g.hold(0.5)
takes 2 inputs (signal, and sampling frequency) with variability 0.5,hold(150, 0)
takes one input and samples it at 150Hz with variability 0limiter(float, float)
look ahead limiter. first param is attack time, second is release time (in seconds)limiter_stereo(float, float)
(same)reverb_stereo(float, [float], [float])
(room size, reverberation time, damping) when damping isn't provided it defaults to 1, time defaults to 5reverb_mono(float, [float], [float])
same but input is passed to both channels and output is joined into onetap(float, float)
(min delay time, max delay time)tap_linear(float, float)
(same)samp_delay(float)
argument is max delay time (in samples), node takes 2 inputs (signal, delay time in samples)
math
add(float, [float], [float], ...)
(up to 8 params)sub(float, [float], [float], ...)
(same)mul(float, [float], [float], ...)
(same)div(float, [float], [float], ...)
(same)rotate(float, float)
t()
time since the node started processing (subsampled every ~2 ms)rise()
one sample trigger when there's a rise in inputfall()
same but fall>([float])
e.g.>()
takes 2 inputs and compares them.>(3)
takes one input and compares against 3<([float])
(same from this one...)==([float])
!=([float])
>=([float])
<=([float])
min([float])
max([float])
pow([float])
mod([float])
orrem([float])
log([float])
bitand([float])
bitor([float])
bitxor([float])
shl([float])
shr([float])
(.. all the way to this one)clip([float, float])
e.g.clip()
takes 1 input and clips to [-1...1],clip(-5, 5)
clips to [-5...5]wrap(float, [float])
wrap between 2 numbers (or between 0 and x if only one number is given)mirror(float, float)
mirror (wave fold) between two valueslerp([float, float])
e.g.lerp()
takes 3 inputs (a, b, t)lerp(3,5)
takes one input (t)lerp11([float, float])
(same..)delerp([float, float])
delerp11([float, float])
xerp([float, float])
xerp11([float, float])
dexerp([float, float])
dexerp11([float, float])
(.. same)abs()
signum()
floor()
fract()
ceil()
round()
sqrt()
exp()
exp2()
exp10()
exp_m1()
ln_1p()
ln()
log2()
log10()
sin()
cos()
tan()
asin()
acos()
atan()
sinh()
cosh()
tanh()
asinh()
acosh()
atanh()
atan2()
hypot()
distance between the origin and a point (x, y)rfft(n, start)
n
is the size of the analysis window, a power of two between 2 and 32768 (inclusive).start
determines the offset within the window to begin writing to (so we can do correct overlap)ifft(n, start)
samepol()
give it cartesian, get polarcar()
give it polar, get cartesiandeg()
give it radians, get degreesrad()
give it degrees, get radiansrecip()
reciprocate. give it x, get 1/xsquared()
cubed()
dissonance()
dissonance_max()
db_amp()
amp_db()
a_weight()
m_weight()
spline()
spline_mono()
soft_sign()
soft_exp()
soft_mix()
smooth3()
smooth5()
smooth7()
smooth9()
uparc()
downarc()
sine_ease()
sin_hz()
cos_hz()
sqr_hz()
tri_hz()
semitone_ratio()
rnd()
rnd2()
spline_noise()
fractal_noise()
normal()
filter nan, inf, and -inf
i hope that everyone will become friends
- tools / dependencies:
- rust https://github.com/rust-lang/rust
- bevy https://github.com/bevyengine/bevy
- bevy_pancam https://github.com/johanhelsing/bevy_pancam
- bevy-inspector-egui https://github.com/jakobhellermann/bevy-inspector-egui/
- fundsp https://github.com/SamiPerttu/fundsp
- cpal https://github.com/rustaudio/cpal
- copypasta https://github.com/alacritty/copypasta
- serde https://github.com/serde-rs/serde
- rosc https://github.com/klingtnet/rosc
- crossbeam https://github.com/crossbeam-rs/crossbeam
- wasm-bindgen https://github.com/rustwasm/wasm-bindgen
- bevy_github_ci_template https://github.com/bevyengine/bevy_github_ci_template
- tracy https://github.com/wolfpld/tracy
- gnuplot http://gnuplot.info/
- calc https://github.com/lcn2/calc
- vim https://github.com/vim/vim
- void linux https://voidlinux.org/
- learning / inspiration / used for a while:
- modulus salomonis regis https://github.com/AriaSalvatrice/AriaModules
- network https://github.com/JustMog/Mog-VCV
- csound https://csound.com
- faust https://faust.grame.fr
- plugdata https://github.com/plugdata-team/plugdata
- bevy-cheatbook: https://github.com/bevy-cheatbook/bevy-cheatbook
- bevy best practices https://github.com/tbillington/bevy_best_practices
- knyst https://github.com/ErikNatanael/knyst
- shadplay https://github.com/alphastrata/shadplay
- rust-ants-colony-simulation https://github.com/bones-ai/rust-ants-colony-simulation
- bevy_fundsp https://github.com/harudagondi/bevy_fundsp
- bevy_kira_audio https://github.com/NiklasEi/bevy_kira_audio
- bevy_mod_picking https://github.com/aevyrie/bevy_mod_picking/
- bevy_vector_shapes https://github.com/james-j-obrien/bevy_vector_shapes
- anyhow https://github.com/dtolnay/anyhow
- assert_no_alloc https://github.com/Windfisch/rust-assert-no-alloc
- bevy_mod_osc https://github.com/funatsufumiya/bevy_mod_osc
quartz is free and open source. all code in this repository is dual-licensed under either:
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
at your option.
unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
https://www.youtube.com/playlist?list=PLW3qKRjtGsGZC7V4eKU_tNwszVZAYvKow
Footnotes
-
in early development everything was actually circular, there wasn't a vertices control.. and the word stuck ↩
-
when there is a change on the black hole side, this gets opened automatically. then it gets closed once the data is read. in some ops this is ignored and the op just reads data even if the white hole isn't open ↩