Giter Site home page Giter Site logo

chaosprint / glicol Goto Github PK

View Code? Open in Web Editor NEW
2.0K 31.0 64.0 106.12 MB

Graph-oriented live coding language and music/audio DSP library written in Rust

Home Page: https://glicol.org

License: MIT License

Batchfile 0.03% Shell 0.05% Rust 76.62% HTML 0.31% JavaScript 22.65% CSS 0.33%
rust audioworklet audio web-audio webassembly live-coding computer-music audio-programming dsp wasm

glicol's Introduction


website website npm Discord GitHub

Glicol (an acronym for "graph-oriented live coding language") is a computer music language with both its language and audio engine written in Rust programming language, a modern alternative to C/C++. Given this low-level nature, Glicol can run on many different platforms such as browsers, VST plugins and Bela board. Glicol's synth-like syntax and powerful audio engine also make it possible to combine high-level synth or sequencer control with low-level sample-accurate audio synthesis, all in real-time.

Get started

πŸš€ The Web App

The easiest way to try Glicol:

https://glicol.org

There you can find guides, demos, docs, and apps for collaboration.

Features
  • Near-native, garbage-collection-free and memory-safe real-time audio in web browsers

  • Quick reference in consoles with alt-d

  • The web app automatically loads samples; you can also drag and drop local samples in the browser editor

  • Robust error handling: error reported in console, but previous music will continue!

  • Mix JavaScript code to create visuals with Hydra synth made by @ojack

  • What you see is what you get, i.e. declarative programmering for both code writing and executing: no need to select anything, just change the code and update, Glicol engine will use LCS algorithm to handle adding, updating and removing

  • Decentralised collaboration using yjs and a unique be-ready mechanism

🎁 For Audio Dev

Description
NPM Docs Safe, performant, light-weight and ergonomic audio lib for web apps
Rust Audio Lib Write VST like this Dattorro reverb plugin
Run on Bela Run Glicol DSL on Bela board for quick audio prototyping.

🍿 YouTube Channel

Find Glicol demo vidoes in this playlist.

Philosophy of Glicol

The motivation of Glicol is:

  • to help people with zero knowledge of coding and music production to get started with live coding

  • to offer experienced music coders a tool for quick prototyping and hacking

In NIME community, it is known as:

low entry fee and high ceilings

This is Glicol's philosophy to approach these goals:

  • design the language from a new instrument design perspective

  • embrace the spirit of the internet for a better experience

Reflected in the implementation:

  • Glicol adopts a graph-oriented paradigm

  • Glicol can be used in browsers with zero-installation

Graph-oriented

The basic idea of Glicol is to connect different nodes like synth modules.

All you need to know is the audio input/output behaviour of each node.

Two ways for connecting: >> and ~reference:

// amplitude modulation and lazy evaluation example
// chain with ~ is a ref chain and will not be sent to the DAC

o: sin 440 >> mul ~amp
~amp: sin 1.0 >> mul 0.3 >> add 0.5

It also applies to sequencer and sampler:

// sequencer pattern
// first divide one bar with space
// then further divide each part based on midi number and rest(_)

o: speed 2.0 >> seq 60 _~a _ 48__67
>> sp \blip

// quantity alters probability
~a: choose 60 60 0 0 72 72

As mentioned above, you can try these examples on:

https://glicol.org

If you want, you can even hear how a seq node work:

o: speed 2.0 >> seq 60 _72 _ 48__67 >> mul 0.5

This is actually analogous to how hardware module pass signals.

It is very easy to remember and to get started.

When Glicol is used in education, we can let students see and hear each node, even including 'envelope'.

Just leave the introduction of data types, Object or Function later when we mix JavaScript with Glicol.

Zero-installation

For the audio engine, instead of mapping it to existing audio lib like SuperCollider, I decide to do it the hard way:

  • write the parser in Rust

  • write the audio engine in Rust that works seamlessly with the AST processing

  • port it to browsers using WebAssembly, AudioWorklet and SharedArrayBuffer

The main reason is to explore performant audio in browsers for easy access and live coding collaboration.

The reward is that we now have an Rust audio lib called glicol_synth:

It can run on Web, Desktop, DAW, Bela board, etc.

And one more thing.

To write everything from low-level also opens the door for meta node.

Now I can explain to students, the hello world tone can also be written in this way:

o: meta `
    output.pad(128, 0.0);
    for i in 0..128 {
        output[i] = sin(2*PI()*phase) ;
        phase += 440.0 / sr;
    };
    while phase > 1.0 { phase -= 1.0 };
    output
`

Roadmap

  • 0.1.0 hello world from dasp_graph and pest.rs, pass code from js to wasm, and lazy evaluation
  • 0.2.0 pass samples from js to wasm, support error handling, bpm control in console
  • 0.3.0 build complex node plate reverb using basic node from glicol, using macro in Rust
  • 0.4.0 use LCS algorithm and preprocessor for smooth and efficient whole graph updating
  • 0.5.0 build const_generics to dasp_graph and use it in glicol, use SharedArrayBuffer, support local sample loading
  • 0.6.0 refactor the code to modules:
    • glicol-main = glicol-synth + glicol-parser + glicol-ext
    • glicol-ext = glicol-synth + glicol-parser + glicol-macro
    • glicol-js = glicol-main + glicol-wasm
  • 0.7.0 support mixing js with glicol in glicol-js using Regex; add visualisation
  • 0.8.0 embed Rhai in glicol πŸŽ‰
  • 0.9.0 redesigned architecture; see the release note
  • 0.10.0 run as a VST plugin
  • 0.11.0 run on Bela
  • 0.12.0 distribute as a npm package
  • better music expressions, more variation for seq nodes
  • exploring new forms of musical interactions

Note that Glicol is still highly experimental, so it can be risky for live performances. The API may also change before version 1.0.0.

Please let me know in issues or discussions:

  • your thoughts on the experience of glicol
  • new feature suggestion
  • bug report, especially the code that causes a panic in browser console
  • missing and confusion in guides and reference on the website

glicol's People

Contributors

bthj avatar chaosprint avatar itsjunetime avatar jengamon avatar vgel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

glicol's Issues

`macro` style? method for developing new nodes using existed nodes

Summary

For example, the following code will play a kick drum sequence:

bd: sin ~pitch >> mul ~env >> mul 0.9

~trigger: speed 4.0 >> seq 60

~env: ~trigger >> envperc 0.01 0.4

~env_pitch: ~trigger >> envperc 0.01 0.1

~pitch: ~env_pitch >> mul 80 >> add 60

What if we write it as a macro named kick:

bd: sin ~pitch >> mul ~env >> mul 0.9

~env: &input >> envperc 0.01 0.4

~env_pitch: &input >> envperc 0.01 0.1

~pitch: ~env_pitch >> mul #pitch >> add #shift

Then we can write:

out: speed 4.0 >> seq 60 >> kick 80 60

What is actually sent to the engine becomes:

~left: speed 4.0 >> seq 60

bd: sin ~pitch >> mul ~env >> mul 0.9

~env: ~left >> envperc 0.01 0.4

~env_pitch: ~left  >> envperc 0.01 0.1

~pitch: ~env_pitch >> mul 80 >> add 60

Pros

  • real-time node definition
  • help to educate the idea of macros

Cons

  • may undermine the name space
  • may cause confusion on the code (readability)

Add missing help entries for tokens used in tour

Hey! I just found this project on Reddit, very cool idea and even better website and introduction! Amazing work so far.

While going through the tour and using the VERY helpful ALT+D feature to see details about the tokens, I noticed that in chapter 4. sequencer there are tokens that don't have any help entries. I have a basic understanding of how digital sound works, but bd is an example of a token where I'd definitely need a help entry. In total, I found the following ones to be missing:

  • bd (sn, hh)
  • sawsynth (and consequently squsynth and trisynth)
  • plate
  • meta

Again, as a newbie I think this is a really really helpful feature, once it's complete it will be a perfect introduction!

LPF update error

update from:

t1: noise 42 >> lpf 300 1.0

to:

t1: noise 42 >> lpf ~mod 1.0
~mod: sin 0.1 >> mul 2000 >> add 3000

will cause an error

filter explode with attack 0.0

Seems like the reverb and/or RLPF are blowing up with an attack time of 0.0.

Steps to reproduce:

  • run the example on frontpage ( https://glicol.org/ )
  • change envperc attack from 0.01 to 0.0
  • update

-> no sound. Run and stop does not help. i can only get the sound back if i refresh the page.

(macOS 10.14, Firefox 89)

If i take out the plate and the filter i can use an instant attack.

The example with effects removed. This works...

~a: choose 48 55 51 58

~b: choose 36 60 0 0 0 0 0

// how about changing the speed to 4.0 and 
//click the update button above?
~trigger: speed 8.0 >> seq ~a ~b >> mul 2.0

~env: ~trigger >> envperc 0.0 0.1 >> mul 0.2

~pitch: ~trigger >> mul 261.626

lead: saw ~pitch >> mul ~env
>> mul 0.6

~cut: squ 0.5 >> mul 3700.0 >> add 4000.0

(i'm completely new to Glicol so sorry if i'm missing something obvious here)

Possibility for meta node manipulation

It would also be great to support dynamic node.

o: sin 440 >> dnode "for i in range(128): output[0][i] = input[0][i] * 0.5"

grame-cncm/faust#685 (comment)

Edit:

I have been looking for options for this embedding language and finally landed on rhai.

the syntax of rhai is really intuitive and I think it has merged the syntax sugar of js and rust.

embedding Rhai in Glicol is a very straightforward experience thanks to its well-designed api and docs (rhai.rs)

now, users can write:

a: script `
    output.pad(128, 0.0);
    for i in 0..128 {
        output[i] = sin(2*PI()*phase) ;
        phase += 440.0 / sr;
    };
    while phase > 1.0 { phase -= 1.0 };
    output
` >> script `
    output = input.map(|i|i*0.2);
    output
`

see the interactive example on https://glicol.org/guide#rhai2

from my current testing, for this real-time audio scripting, creating new variables, especially arrays should be avoided.

for now the script node provides outout, input (if there is one), sr and phase to the Scope of rhai, as well as variables from a to z. x0, x1, x2, y0, y1, y2.

I am still exploring the possibility to share a ringbuf in rhai Scope; some functions such as random can also be useful.


Add an example:

// a sawtooth osc chained with a onepole filter
a: meta `
	f = 220.;
	output.pad(128, 0.0);
	if phase == 0 {
		p = 0.0;
	}
	for i in 0..128 {
		output[i] = p * 2. - 1.;
		p += f / sr;
	};
	if p > 1.0 { p -= 1.0 };
	output
` >> meta `
	r = 1./2000.;
	if phase == 0.0 {
		z = 0.0
	}
	output.pad(128, 0.0);
	b = (-2.0 * PI() * r).exp();
	a = 1.0 - b;
	for i in 0..128 {
		y = input[i] * a + b * z;
		output[i] = y;
		z = y;
	};
	output
`

TODOs:

  • Solve the popping during update
  • Improve the performance

Semantics for the reference used alone

In Glicol current implementation, the reference can be used alone.

~trigger: imp 1.0;
out: ~trigger >> sp \808bd_0

This is more like a syntax suger for:

~trigger: imp 1.0;
out: const_sig ~trigger >> sp \808bd_0

However, this does not work:

~tri: envperc 0.001 0.1

~lead: saw 100.0 >> mul ~tri

out: imp 1.0 >> ~tri

It seems the ref when used alone, can only output signal, but cannot take any input.
In the case that does not work above, should we connect the imp 0.1 to the beginning of ~tri chain?
But this is not good.
As it is still not making any sound.

Better syntax for wrapping JavaScript code

Currently, glicol-js uses regex to find everything between {{}} as js code:

o: sin {{42*10+20}}

But apperently due to the limitation of JS's regex limitation, you cannot write any {} in your js code.
This can be changed by using some other symbols.
Some options:

o: sin "42*10+20"
o: sin %42*10+20%
o: sin |42*10+20|

Any suggestions?

Edit:

I am now settled with:

o: sin ##42*10+20#

Need this asymmetric style for regex.

Uncaught TypeError: Cannot perform %TypedArray%.prototype.set on a detached ArrayBuffer

~aa: loop 60 60 60 60 >> sampler \bd

~bb: loop 60 _67 _62 _65 >> sampler \bass

~cc: loop _ _75 80 60 70 ___80 __75 >> sampler \can

&dd: sin 1.0 >> mul 0.3 >> add 0.5

~dd: loop 62 67 _58 64 62 _67 _58 64 >> sampler \808hc >> mul &dd

~ee: loop _ 60 _ 60 >> sampler \jazz

start time

23:20:01.203 engine.js:76 samplePtr, Length 5597696 22051

problem time

23:21:46.576 engine.js:118 Uncaught TypeError: Cannot perform %TypedArray%.prototype.set on a detached ArrayBuffer
    at Float32Array.set (<anonymous>)
    at AudioWorkletProcessor.process (engine.js:118)

add lazy evaluation

currently we can use reference as a node, but cannot use it before defining the ref

is it necessary to add lazy evaluation?

probably add bpm control

is it necessary to have a separate bpm control?

it is not a node. hence it is better to have some macro command syntax e.g. !bpm 200

or we can use speed 1.5 in each chain, which is already implemented.

Work on the Glicol book/wiki

There are some feedbacks that some concepts on the website can be too difficult for beginners. Admittedly, this needs some further work.
But it would be almost impossible to let a beginner know some concepts in a limited spaces (website). I would stick to the current design as it is exclusively minimal and clean. Of course, if I expand it to a full page, with more words, pictures, there will be more information. But in my opinion it will be very messy.
It is all about the balance between simplicity and information amount. The idea to provide examples is to let users get sound and build an intuitive understanding even when they don't understand the technology (of course, it would be good if they do).
One solution is to build a more detailed docs like wiki or book, but this takes some time.
It is great to do this work but based on my experience, to understand concepts like graph will take some time, months or even years.

Publish as reusable library to NPM

Hey! Not sure if this is already on your roadmap, but I wanted to look into what it would take to publish this to NPM so it can easily be used in other projects. There's a couple of changes that would have to be made to the JS code, mostly to make it more robust and to improve the way dependencies are loaded and resolved (especially towards the global window), but it's not a lot.

If you're interested I could help out on this front, I already have a bit of experience with this kind of thing.

PS: If this is released to NPM, it would also be a good idea to add some type definitions - since your public API is very small it would just be one function right now. However there is actually something REALLY REALLY cool you could do here. Since a couple of versions ago Typescript supports advanced string types, which allow you to do a lot of validation and so on on the type system. I for example have implemented a small ASM parser with it, and it works wonderfully. It shouldn't be too hard to implement a generator for validating GLICOL strings on a type level using your API file. Could be a fun and useful extension to your project, allowing Typescript users compile-time verification of their GLICOL input!

Add details about performance impact in website scenario

Hey! Since GLICOL integrates so well into a website scenario, it'd be great to have some measurements regarding resource usage. If I for example were to use this library to generate background music in a game, what's the effect on my performance budget? Considering the implementation in WASM I'd expect the performance impact to be very small, which would be a nice additional argument for the library :)

I've been thinking a bit about ways to automate this measurement, potentially even to measure and prevent performance degradation over time. This is probably a lot easier on the Rust side since it's difficult to measure this type of data accurately in a browser environment - you could either try Puppeteers page.metrics or something like PageSpeed Insights (e.g. through an Action).

Maybe I'm overthinking this and this isn't necessary, but since I did look around a bit I thought I'd share what I've found :)

nodes deleted before an error node is detected

when an error node is detected, the graph should remain to be the prior working one. but the current implementation makes the deleting in order. therefore, those nodes data cannot be recovered once they are deleted prior to an error occurrence.

Some thoughts after teaching Glicol in MUS 2830 UiO

  • it is better to have an even easier way to load samples from online resources. loading samples from local machine should be seen as a more advance feature.
  • beginners, especially non-programmers can make any kind of syntax errors. the feedback for errors such as a missing colon, should be instant.
  • the collaboration should have a force run mechanism.

Towards no-panic: the error handling system

Currently, the pest parser generates an AST, which is actually a HashMap of NodeIndex chains.

The NodeIndex is calculated in the process of parsing.

let node_index = graph.add_node(MyNode::new(paras));

The error handling mechanism is to backup the previously successful code. Once there is an error during the parsing or node creation, backup code will be used.

This brings an issue that if there is an error in the halfway, half of the graph will be modified. And we need to create a new graph (using the old backup code) to replace this half-processed graph. This is really redundant.

Therefore, skipping some middleware like Enum of Glicol nodes is actually missing the big picture.

Such a mechanism should be changed, and perhaps switching to nom parser as the error handling of nom can be more promising.

Update:

  • Need to make sure when an error is found, everything should run as normal. So far some Message before the error position could have been already sent out.

Communication like MIDI or OSC

WebMIDI can be used. We also need to consider the behaviour of this node on platforms other than the web.

The general plan is to give the Rust struct Engine an input and output property that supports the communication.

Optimisation: only connect clock to those nodes that need it

sin, saw, tri, squ, pha, imp need clock.
the rest don't.
no need to connect clock to every node.
of course a more low-level solution is to customise dasp_graph and process each node only once.
if the node is processed, use the previous data. is it doable?

This updating will cause panic!

From:

~aa: sin 100

lead: ~aa

~ab: saw 50 >> mul 0.1

To:

~aa: sin 100

lead: ~ab

~ab: saw 50 >> mul 0.1

Possibly caused by the LCS algorithm.

range error in live coding

error:

lib.rs:55 Uncaught RuntimeError: unreachable
    at __rust_start_panic (<anonymous>:wasm-function[407]:0x38d9a)
    at rust_panic (<anonymous>:wasm-function[404]:0x38cdb)
    at _ZN3std9panicking20rust_panic_with_hook17hd5a9721517b5ba3aE (<anonymous>:wasm-function[399]:0x389be)
    at rust_begin_unwind (<anonymous>:wasm-function[398]:0x3889d)
    at _ZN4core9panicking9panic_fmt17haa65c7c3d08f0debE (<anonymous>:wasm-function[466]:0x3cd54)
    at _ZN4core3str16slice_error_fail17h9ddacaa3b3a1608aE (<anonymous>:wasm-function[461]:0x3c777)
    at _ZN4core3str6traits103_$LT$impl$u20$core..slice..SliceIndex$LT$str$GT$$u20$for$u20$core..ops..range..RangeTo$LT$usize$GT$$GT$5index28_$u7b$$u7b$closure$u7d$$u7d$17h7d218b773077e51dE (<anonymous>:wasm-function[216]:0x1f65f)
    at _ZN6glicol6Engine16gen_next_buf_12817he0fea18fac2d4c17E (<anonymous>:wasm-function[225]:0x242c2)
    at process (<anonymous>:wasm-function[21]:0x26ef)
    at AudioWorkletProcessor.process (https://glicol.web.app/worklet/engine.js:144:44)

code:

aa: speed 4.0 >> seq ~a >> sampler \bass

~a: choose 60 70 0

bb: seq _ 90 >> sampler \feelfx

cc: seq 60 >> sampler \diphone

dd: seq 72 _ 77_75_ 74 >> sampler \gtr

ee: seq 60 >> sampler \incomming

iOS Safari support?

Unfortunately the homepage example plays no audio on iOS safari/firefox. Perhaps this isn’t resolvable due to API limitations

Cannot perform Construct on a detached ArrayBuffer at new Uint32Array (<anonymous>)

Reproduction:

Using sampleFolder() in Chrome, macOS, and run a simple sample playback code:
aa: seq 60 >> sp \808_0

Error:

Uncaught TypeError: Cannot perform Construct on a detached ArrayBuffer
    at new Uint32Array (<anonymous>)
    at allocUint32Array (96356af5-9b66-4154-ab87-78d40eac3206:240)
    at MessagePort.GlicolEngine.port.onmessage (96356af5-9b66-4154-ab87-78d40eac3206:316)
allocUint32Array @ 96356af5-9b66-4154-ab87-78d40eac3206:240
GlicolEngine.port.onmessage @ 96356af5-9b66-4154-ab87-78d40eac3206:316

Output of npx envinfo --system --npmPackages vite,@vitejs/plugin-vue --binaries --browsers:

System:

  OS: macOS 11.2
  CPU: (4) x64 Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
  Memory: 178.39 MB / 8.00 GB
  Shell: 5.8 - /bin/zsh

Binaries:

  Node: 10.16.0 - /usr/local/bin/node
  Yarn: 1.17.3 - /usr/local/bin/yarn
  npm: 6.10.1 - /usr/local/bin/npm

Browsers:

  Chrome: 92.0.4515.131

Support automatic reloading samples when the engine restart

Loaded samples are sent to the engine.
But when the engine restarts, either caused by panic or the user's choice, the samples need to be reloaded.
This is too cumbersome.
Possibly save the loaded samples in JS side and when the engine restart, send the saved samples again

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.