{"id":16138176,"url":"https://github.com/ul/sound-garden","last_synced_at":"2025-04-06T17:24:06.903Z","repository":{"id":146517246,"uuid":"146135203","full_name":"ul/sound-garden","owner":"ul","description":"A personal journey into the audio synth domain","archived":false,"fork":false,"pushed_at":"2018-10-29T07:20:20.000Z","size":425,"stargazers_count":25,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-04T12:07:32.347Z","etag":null,"topics":["dsp","nim","nim-lang","nim-language","sound","synth"],"latest_commit_sha":null,"homepage":null,"language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ul.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-25T22:54:22.000Z","updated_at":"2024-05-29T18:31:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"293dd169-9771-4002-b249-d4986362c9fc","html_url":"https://github.com/ul/sound-garden","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fsound-garden","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fsound-garden/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fsound-garden/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ul%2Fsound-garden/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ul","download_url":"https://codeload.github.com/ul/sound-garden/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247519283,"owners_count":20951953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsp","nim","nim-lang","nim-language","sound","synth"],"created_at":"2024-10-09T23:32:40.349Z","updated_at":"2025-04-06T17:24:06.882Z","avatar_url":"https://github.com/ul.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Sound Garden\n\n(consider reading this document at http://ul.mantike.pro/sound-garden/README.html because GitHub parses AsciiDoc improperly)\n\n== What?\n\nSound Garden is a text based modular synthesis environment. If you ever considered text like this\n\n----\n.0625 .5 p 110 s * dup .5 t 0.0625 5 range 0.5 fb swap .125 t 0.0625 5 range 0.5 fb + .1 * \n---- \n\nto be a valid musical notation and sound like https://soundcloud.com/ruslan-prokopchuk/rf-01[this]\nto be a subject of interest then you might find Sound Garden worthwhile having a look at.\n\n== Why?\n\nThere is Sporth, Extempore, SuperCollider, PureData and other excellent mature environments out there, why to build own? Because I want to understand how audio synth works and true understanding comes as an ability to re-create subject from the scratch in a different form. http://ul.mantike.pro/SoundGardenManifesto[More]\n\n== How?\n\nContinue reading, the rest of the document is Sound Garden manual.\n\n== Prerequisites\n\n* https://nim-lang.org[Nim] 0.19.0\n* http://libsound.io[libsoundio] 1.1.0\n* http://www.mega-nerd.com/libsndfile[libsndfile] 1.0.28\n* http://liblo.sourceforge.net/[liblo] 0.29\n* https://github.com/nsf/termbox[termbox] 1.1.2\n* https://github.com/casey/just[just] 0.3.12\n* https://github.com/watchexec/watchexec[watchexec] 1.9.2\n* https://github.com/hanslub42/rlwrap[rlwrap]\n\n----\n$ brew install nim libsoundio libsndfile liblo termbox just watchexec rlwrap\n----\n\n== Build\n\n----\n$ just build\n----\n\n== Interfaces\n\nSound Garden has three interfaces:\n\n[horizontal] \nREPL:: Simple language is available for quick experiments with signals. \nTUI:: Pseudographics mode to create and connect nodes of signal-producing code. \nAPI:: You could drop Sound Garden into your Nim project and use its audio subsystem and unit generators. Right now this interface is not documented.\n\nAll interfaces use the same underlying audio signals system. Signal is a function of audio context\nto sample value. Audio context provides the signal with a sample rate, current sample number and\ncurrent channel. Thus every signal is inherently multichannel, but it can choose to produce the\nsame sample value for each channel or produce silence in some of them. At the moment the maximum\nnumber of channels is hardcoded to 2. Signals could be closures which capture other signals and call\nthem against audio context to use their value. This is the way the audio graph is built in Sound Garden. Root\nsignal then passed to the audio stream and called every sample to produce sound.\n\n== REPL\n\nSound Garden REPL is operated via simple stack-based language. The only type of element to be put\non stack is a signal. There are 4 such stacks available (it's hardcoded but could be tuned). Sound\nGarden plays top element of each stack in a separate sound stream, simultaneously. Multiple stacks\nnavigation and element transfer is covered in details at the end of the section.\n\n=== Run\n\n----\n$ just run\n----\n\n=== First steps\n\nFirst things first, to exit REPL press CTRL-D or type `quit` and press return. To quickly replace\ncurrent sound in the active stack with silence, enter `0` and press return.\n\nThe simplest signal is just a constant value. To put it on the current stack type a number in the REPL and press return:\n\n----\n[0]\u003e 440\n[0]\u003e\n----\n\nLet's make some sound! `440` is a good candidate to be a frequency, let's put initial phase `0` onto stack and use `sine` unit generator:\n\n----\n[0]\u003e 0\n[0]\u003e sine\n[0]\u003e\n----\n\nWhat happened? `sine` ugen requires 2 input signals. It consumed them from the top of the stack and\nproduced new signal which was put back on the top of the current stack.\n\nTo ease such kind of composition, Sound Garden provides two conveniences:\n\n* Words hasn't to be entered one by one. Just separate them with spaces and enter in one go: `440 0 sine`\n* There are shortcuts for some frequently used ugens and their default inputs. E.g. `s` is a shortcut for `sine` which expects only one input, frequency, and sets initial phase to 0: `440 s`\n\nArithemics operators are lifted to signal space: `440 s .1 *`. Note that first we need to put both\ninputs on the stack (`440 s` and `.1`) and only then apply `*` to them. Also it worth repeating\nthat ugens consume their inputs from the stack and put their outputs back on the stack, thus after\nexecuting `440 s .1 *` you will end up with one signal on the stack.\n\nBecause Sound Garden operates exclusively on signals, `sine` frequency has not to be constant: `2 s 1 + 220 *\ns` is completely legitimate and would produce a wobbling sound.\n\n=== Stack inspection\n\nTo check contents of the current stack use the word `dump`:\n\n----\n[0]\u003e dump\n⓪ 440.0\n[0]\u003e s \n[0]\u003e dump\n⓪ 440.0 0.0 sine\n[0]\u003e\n----\n\nNote that Sound Garden replaced shortcut with a full form. Sound Garden does its best to label elements on the\nstack in the way they could be just copy-pasted to create the same signal, but it's not always\npossible.footnote:[Due to timing and node multicast issues.] Also, `dump` shows only top 10 elements\nof the current stack, to list all elements use `dumpAll`.\n\nIt's possible to draw a nice graph of the audiowave in the first channel of current sound stream\nusing the word `wave`. This word could be parametrized with a step: `wave:64` would probe the\naudiowave every 64th sample.\n\n----\n[0]\u003e wave\n▲ 1.0\n⠤⠒⠊⠉⠉⠉⠉⠒⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠔⠒⠉⠉⠉⠉⠑⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠒⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⢄⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠢⡀⠀\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒\n⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠒⠤⢄⣀⣀⣀⣀⠤⠤⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n▼ -1.0\n[0]\u003e\n----\n\nSummary:\n\n[horizontal] \ndump:: show top 10 elements of stack\ndumpAll:: show all elements of stack\nwave:\u003cN\u003e:: plot sound of first channel of current stream, taking measure each N samples\n\n=== Stack manipulations\n\n[horizontal] \nempty:: remove all elements in stack\npop:: remove top element\ndup:: duplicate top element, a -\u003e a a \nswap:: swap top element with the next one, a b -\u003e b a\nrot:: take 3rd from the top element and put it on the top, a b c -\u003e b c a\n\n=== Oscillators\n\nAll non-hyperbolic oscillators produce signal in range -1..1\n\n[horizontal] \nsaw:: (freq, phase0) -\u003e saw oscillator\nw:: (freq) -\u003e saw with phase0 = 0 \ntri:: (freq, phase0) -\u003e triangle oscillator (symmetric)\nt:: (freq) -\u003e tri with phase0 = 0 \npulse:: (freq, width, phase0) -\u003e rectangular oscillator with width of positive segment as a ratio of period\np:: (freq, width) -\u003e pulse with phase0 = 0\nsine:: (freq, phase0) -\u003e sine oscillator\ns:: (freq) -\u003e sine with phase0 = 0 \ncosine:: (freq, phase0)\ntangent:: (freq, phase0)\nhsine:: (freq, phase0) -\u003e hyperbolic sine oscillator\nhcosine:: (freq, phase0)\nhtangent:: (freq, phase0)\n\n=== Basics\n\n[horizontal] \nsilence:: () -\u003e alias for constant 0 signal\nwhiteNoise, noise, n:: () -\u003e each sample in each channel is the next value provided by pseudo-random generator\nNote that this signal is not multicasted and will output different samples for the same channel and sample number when used as an input for different unit generators\n\nproject:: (x, a, b, c, d) -\u003e assuming that signal x varies in the range from a to b linearly project its values to the range from c to d\nNote that ranges are just signals and are allowed to vary in time \n\nrange, r:: (x, c, d) -\u003e same as project with a = -1 and b = 1 \nunit:: (x) -\u003e same as range with c = 0 and d = 1 \ncircle:: (x) -\u003e same as range with c = -π and d = π \nsh:: (trigger, x) -\u003e sample and hold\ndb2amp, db2a:: (x) -\u003e decibels to amplitude, base amplitude assumed to be 1.0\namp2db, a2db:: (x) -\u003e amplitude to decibels, base amplitude assumed to be 1.0\nfreq2midi, f2m:: (x) -\u003e frequency to midi pitch\nmidi2freq, m2f:: (x) -\u003e midi pitch to frequency\nquantize:: (x, step) -\u003e round signal x values to the nearest step multiplicative\ninput, in:: () -\u003e microphone input. Must be enabled via `--with-input` flag: `just run --with-input`\nch0:: (x) -\u003e compute only channel 0 of signal and broadcast it to all channels\nch1:: (x) -\u003e compute only channel 1 of signal and broadcast it to all channels\n\n=== Math\n\nBinary arithmetic operations are available: `+`, `-`, `*`, `/`, `mod`. If you prefer, you can use aliases `add`, `sub`, `mul`, `div`.\n\nComparison operators `==`, `!=`, `\u003c`, `\u003c=`, `\u003e`, `\u003e=` return 1 when comparison is true, and 0 otherwise.\n\nLogic operators:\n\n[horizontal] \nand:: (a, b) -\u003e returns 1 only when both a and b values are equal to 1, otherwise 0\nor:: (a, b) -\u003e returns 1 only when either a or b value is equal to 1, otherwise 0\n\nNote that logic operators semantics are not finalized yet. Please feel free to propose your version.\n\n[horizontal] \nmin:: (a, b)\nmax:: (a, b)\nclip:: (x) -\u003e forces signal values to be in the range -1..1 by outputting nearest edge for values outside\nwrap:: (x) -\u003e forces signal values to be in the range -1..1 by wrapping it around the range \nexp:: (x) -\u003e e^x\nsin:: (x)\ncos:: (x)\ntan:: (x)\nsinh:: (x)\ncosh:: (x)\ntanh:: (x)\nclausen:: (x) -\u003e Clausen function. Note it's expensive to compute\nround:: (x) -\u003e round signal value to the nearest integer\n\n=== Filters\n\n[horizontal] \nlpf:: (x, freq) -\u003e https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter[Simple infinite impulse response low-pass filter]\nhpf:: (x, freq) -\u003e https://en.wikipedia.org/wiki/High-pass_filter#Algorithmic_implementation[Simple infinite impulse response high-pass filter]\nbqlpf, l:: (x, freq) -\u003e biquad LPF as described https://shepazu.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html[here]\nbqhpf, h:: (x, freq) -\u003e biquad HPF as described https://shepazu.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html[here]\nprime:: (x) -\u003e delay x by one sample\ndelay:: (x, time) -\u003e max delay time is 60 seconds at 48000 sample rate\nfb:: (x, delay, gain) -\u003e feedback echo, max delay is 60 seconds at 48000 sample rate\n\n=== Triggers\n\n[horizontal]\nmetro:: (freq) -\u003e emit 1.0 with given frequency, 0.0 all other time\ndmetro:: (period) -\u003e emit 1.0 every given period, 0.0 all other time\n\n=== Envelopes\n\n[horizontal]\nimpulse:: (trigger, apex) -\u003e generate exponential impulse which reaches 1.0 in apex seconds and then fades \nadsr:: (gate, a, d, s, r) -\u003e classic ADSR envelope\nline:: (target, time) -\u003e when `target` changes, smooth the transition linearly over `time` period\n\n=== Modulation\n\n[horizontal]\nfm:: (carrierFreq, modulationFreq, modulationIndex) -\u003e frequency modulated sine oscillator\npm:: (carrierFreq, modulationFreq, modulationIndex) -\u003e phase modulated sine oscillator\ncheb2:: (x) -\u003e Chebyshev polynomial of degree 2\ncheb3:: (x) -\u003e Chebyshev polynomial of degree 3\ncheb4:: (x) -\u003e Chebyshev polynomial of degree 4\ncheb5:: (x) -\u003e Chebyshev polynomial of degree 5\ncheb6:: (x) -\u003e Chebyshev polynomial of degree 6\ncheb7:: (x) -\u003e Chebyshev polynomial of degree 7\ncheb8:: (x) -\u003e Chebyshev polynomial of degree 8\ncheb9:: (x) -\u003e Chebyshev polynomial of degree 9\n\n=== Analyzers\n\n[horizontal]\npitch:: (x) -\u003e pitch detector, implemented as YIN algorithm with block size of 1024 samples and threshold 0.2\n\n=== Variables\n\n[horizontal]\nvar:\u003cNAME\u003e:: (x) -\u003e take a signal from the top of stack, wrap it into the variable NAME and put variable back on the stack\nset:\u003cNAME\u003e:: (x) -\u003e consume a signal and assign it to the variable NAME\nget:\u003cNAME\u003e:: () -\u003e signal which current value is the same as of signal in the variable NAME\nunbox:\u003cNAME\u003e:: () -\u003e put signal assigned to the variable NAME on the top of stack; difference from `get` is that when new signal will be assigned to the variable unboxed one will stay the same \n\nNote that you need to assign variable via var or set before using it. Exceptions is lowercase one-letter variables from 'a' to 'z', they are pre-assigned with constant signal 0 on the start. \n\n=== OSC\n\nSound Garden embeds OSC server. To start it listening to the port 7770 pass `--with-osc` flag: `just run --with-osc`.\nAvailable endpoints:\n\n[horizontal]\n/interpret:: s -\u003e interpret string s as if it was entered in the REPL\n/set/NAME:: f -\u003e set special OSC variable NAME to the constant signal of f\n\nTo access OSC variables from the REPL use\n\n[horizontal]\nosc:\u003cNAME\u003e:: () -\u003e value of OSC variable, 0 if it was not set yet\n\n=== Tables\n\n[horizontal]\nwritetable:\u003cNAME\u003e:\u003cN\u003e, wt:\u003cNAME\u003e:\u003cN\u003e:: (trigger, x) -\u003e on trigger write N samples (for each channel) of signal x to the table NAME. It puts a signal back on the stack which passes through x values.\ndurwritetable:\u003cNAME\u003e:\u003cD\u003e, dwt:\u003cNAME\u003e:\u003cD\u003e:: (trigger, x) -\u003e on trigger write D seconds (for each channel) of signal x to the table NAME. It puts a signal back on the stack which passes through x values.\nreadtable:\u003cNAME\u003e:: (indexer) -\u003e read from the table using indexer signal as a position in seconds, with linear interpolation.\nloadtable:\u003cNAME\u003e:\u003cPATH\u003e, lt:\u003cNAME\u003e:\u003cPATH\u003e:: () -\u003e load file from the PATH into the table NAME; note that sample rate and channels count of the file must align with current stream ones\nsavetable:\u003cNAME\u003e:\u003cPATH\u003e, st:\u003cNAME\u003e:\u003cPATH\u003e:: () -\u003e save the table NAME into the file at PATH in 24-bit PCM WAV format\n\n=== Multiple stacks\n\nNumber of the current stack is displayed in the REPL prompt in brackets:\n\n----\n[0]\u003e next\n[1]\u003e\n----\n\nAll stack navigation commands wraps, i.e. if current stack is the last one then any command referencing \"next\" stack would operate on the first one and vice versa.\n\n[horizontal]\nnext:: () -\u003e switch to the next stack\nprev:: () -\u003e switch to the previous stack\nmv\u003e:: (x) -\u003e move signal to the next stack\n\u003cmv:: (x) -\u003e move signal to the previous stack\nmv\u003c:: (x) -\u003e move signal from the next stack\n\u003emv:: (x) -\u003e move signal from the previous stack\ncp\u003e:: (x) -\u003e copy signal to the next stack\n\u003ccp:: (x) -\u003e copy signal to the previous stack\ncp\u003c:: (x) -\u003e copy signal from the next stack\n\u003ecp:: (x) -\u003e copy signal from the previous stack\n\n== TUI\n\n=== Build \u0026 Run\n\n----\n$ just tui\n----\n\nPass `--with-input` if you want to use input signal: `just tui --with-input`\nPass `--with-osc` to start OSC server: `just tui --with-osc`\nOr pass both ;-)\n\nText user interface mode provides an extension to the REPL mode. It allows to organize snippets of code in\nthe same language as in REPL into a graph of interconnected nodes. Each node have its own stack\ninitially filled with its input nodes signals. Source code in the node is applied to this stack and\nthe top element of resulting stack is used as the output of node.\n\nLet's look into the anatomy of node:\n\n----\n╔═════════╤═══╗                     \n╫ 10 8 11 ┼ 0 ╫                   \n╟─────────┴───╢                  \n║ + s *       ║                \n╚═════════════╝ \n----\n\nFirst row consists of indices of input nodes and then index of the current node. Output signals of\ninput nodes are put into the stack of current nodes from left to right. In the example above signal\nfrom the node 10 will be on the bottom of the stack and signal from node 11 will be on the top. Then\ncode `+ s *` is executed against that stack. Top element of resulting stack is what other nodes\nwill consume if they reference node 0. Also, nodes with indices from 0 to 7 (inclusive) are special\nbecause their output signal is also played in node's audio stream.\n \nTo move entire canvas just press left moust button on any space free of nodes and drag around.\nTo move the node on canvas, press left mouse button on the node and drag. To edit node's inputs\nright-click on the node's inputs area. To edit node's source code right-click on the node's source\narea. To commit changes left-click somewhere or press return. We recommend to edit inputs first as\nunused signals on the stack would not harm, but having source code which exhaust stack could lead to\nan error.\n\nTo quickly save current nodes configuration press `/` (slash). It will be written into the `dump.txt` file in the current working directory. To load configuraion from that file press `\\` (backslash).  \n\nTo quit TUI press escape.\n\nObviously, multiple stacks navigation and manipulation commands are not available for use in nodes\ncode. Another limitation is that `/interpret` OSC endpoint doesn't make much sense in TUI mode\nbecause streams are not connected to REPL stacks operated by interpret. Everything else should work\nfine.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ful%2Fsound-garden","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ful%2Fsound-garden","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ful%2Fsound-garden/lists"}