diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-05-17 15:41:56 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-03-02 14:24:49 +0000 |
| commit | 406da94f13f47a9c7aef6b9c11cfe057772630cf (patch) | |
| tree | dd98e12eddd421e2883c90239cb10225bb75b07e /docs/guide | |
| parent | builtin/trae: print literals literally (diff) | |
| download | alive-406da94f13f47a9c7aef6b9c11cfe057772630cf.tar.gz alive-406da94f13f47a9c7aef6b9c11cfe057772630cf.zip | |
split guide into multiple pages
Diffstat (limited to 'docs/guide')
| -rw-r--r-- | docs/guide/basic-types.md | 59 | ||||
| -rw-r--r-- | docs/guide/copilot-gui.png | bin | 0 -> 4445 bytes | |||
| -rw-r--r-- | docs/guide/defining-symbols.md | 37 | ||||
| -rw-r--r-- | docs/guide/evaltime-and-runtime.md | 45 | ||||
| -rw-r--r-- | docs/guide/functions.md | 54 | ||||
| -rw-r--r-- | docs/guide/getting-started-guide.md | 25 | ||||
| -rw-r--r-- | docs/guide/hello-world.md | 33 | ||||
| -rw-r--r-- | docs/guide/importing-operators.md | 26 | ||||
| -rw-r--r-- | docs/guide/installation.md | 53 | ||||
| -rw-r--r-- | docs/guide/making-sound.md | 58 | ||||
| -rw-r--r-- | docs/guide/scopes.md | 82 | ||||
| -rw-r--r-- | docs/guide/syntax.md | 43 | ||||
| -rw-r--r-- | docs/guide/working-with-the-copilot.md | 59 |
13 files changed, 574 insertions, 0 deletions
diff --git a/docs/guide/basic-types.md b/docs/guide/basic-types.md new file mode 100644 index 0000000..c22929c --- /dev/null +++ b/docs/guide/basic-types.md @@ -0,0 +1,59 @@ +Strings can be written in two ways: using double quotes (`"`), as we did above, +or using single quotes (`'`). In both types of strings, you can escape a quote +that otherwise would signify the end of the string by adding a single backslash +before it. Consequently, backslashes also have to be escaped in the same way. +The following are all valid strings: + + "hello world" + 'hello world' + "it's a beautiful day" + 'it\'s a beautiful day' + "this is a backslash: \\" + "this is a double quote: \"" + "" + '' + +Aside from strings, there are two more types of values that you can use when +writing alv programs: numbers and booleans. Numbers use the digits 0-9 and +can be integers, contain a decimal point, or start or end with a decimal point. +Numbers can start with a negetive sign. The following are all valid numbers: + + 0 + 12 + -7 + 0.1 + 10. + .1 + 123. + +There are only two boolean values, `true` and `false`: + + true + false + +The operator [print][], that we have been using above, only works on strings, +but there is a similar operator called [trace][] that can be used to inspect +any kind of value. It prints the value itself alongside more information, such +as the values type. Give it a try: + + (trace "hello") + (trace 2) + (trace true) + +This will print the following: + + changes to files: values.alv + trace "hello": <str= "hello"> + trace 2: <num= 2> + trace true: <bool= true> + +On the left side of the colon, [trace][] prints the expression that it is +evaluating. On the right side, three pieces of information are shown: + +- the *type*: `str`, `num`, `bool` +- the *value* itself: `"hello"`, `2`, `true` +- the *kind* of the result: `=` + +`=` means that these values are *constant* - they will not change by themselves +until the code is changed. For simple values like these that seems obvious, but +in `alv` we can also create values tha change over time, as we will see soon. diff --git a/docs/guide/copilot-gui.png b/docs/guide/copilot-gui.png Binary files differnew file mode 100644 index 0000000..e0b3e25 --- /dev/null +++ b/docs/guide/copilot-gui.png diff --git a/docs/guide/defining-symbols.md b/docs/guide/defining-symbols.md new file mode 100644 index 0000000..5fb6d3f --- /dev/null +++ b/docs/guide/defining-symbols.md @@ -0,0 +1,37 @@ +Another element of code in `alv` that we haven't discussed in detail yet are +*symbols*. *Symbols* (like `trace`, `import*` or `math/+`) are names that serve +as placeholders for previously *defined* values. When code is evaluated, symbols +are looked up in the current *scope* and replaced with the corresponding value +found there. + +When an `alv` file starts running, a number of symbols are defined in the +default scope: These are the *builtins* mentioned above, and of which we have +already been using [trace][], [import][], and [import*][]. + +To *define a symbol* yourself, the [def][] builtin is used. It takes the symbol +as its first, and the value to associate as its second parameter. After a symbol +is defined, the name becomes an alias that behaves like the value itself. For +example, we can use [def][] to associate the result of our calculation with the +symbol `result`, and then refer to it by that symbol in the [trace][] operator: + + (import* math) + + (def result (+ 1 2)) + (trace result) + +Symbols need to start with a letter or one of the characters `-_+*/.=~!?%`. +After the first character, numbers are also allowed. There are two types of +symbols that are treated specially: symbols containing a slash (`math/+`), and +symbols starting and ending with asterisks (`*clock*`): + +- Symbols containing slashes (except at beginning and end of the symbol) are + split into multiple symbols, and looked up recursively in the scope. For + example, `math/+` is found by first looking for a value for the symbol `math`, + and then looking for the symbol `+` in that value. If the value for the + symbol `math` is not a scope, an error is thrown. +- Symbols starting and ending with asterisks are called `dynamic symbols` and + are looked up in a different way inside user-defined functions. This will be + covered in detail later. +- The two special formats can be mixed: when evaluating `*hello*/world`, + `alv` will look for the symbol `world` within the scope found by dynamically + resolving `*hello*`. diff --git a/docs/guide/evaltime-and-runtime.md b/docs/guide/evaltime-and-runtime.md new file mode 100644 index 0000000..e718cf3 --- /dev/null +++ b/docs/guide/evaltime-and-runtime.md @@ -0,0 +1,45 @@ +So far, `alv` may seem a lot like any other programming language - you write +some code, save the file, and it runs, printing some output. "What about the +'continuously running' aspect from the introduction?", you may ask yourself. + +So far, we have only seen *evaltime* execution in alv - but there is also +*runtime* behavior. At *evaltime*, that is whenever there is change to the +source code, `alv` behaves similar to a Lisp. This is the part we have seen +so far. But once one such *eval cycle* has executed, *runtime* starts, and +`alv` behaves like a dataflow system like [PureData][pd], [Max/MSP][max] or +[vvvv][vvvv]. + +What looked so far like static constants are actually *streams* of values. +Whenever an input to an operator changes, the operator (may) update and respond +with a change to its output as well. To see this in action, we need to start +with a changing value. Number literals like `1` and `2`, which we used so far, +are *evaltime constant*, which means simply that they will never update. Since +all inputs to our [math/+][] operator are *evaltime constant*, the result is +constant as well. To get some *runtime* activity, we have to introduce a +side-effect input from somewhere outside the system. + +The [time/][] module contains a number of operators whose outputs update +over time. Lets take a look at [time/tick][]: + + (import* time) + (trace (tick 1)) + +This will print a series of numbers, incrementing by 1 every second. The +parameter to [time/tick][] controls how quickly it counts - try changing it to +`0.5` or `2`. As you can see, we can change [time/tick][] *while it is +running*, but it doesn't lose track of where it was! + +All of the other things we learned above apply to streams of values as well - +we can use [def][] to store them in the scope, transform them using the ops +from the [math/][] module and so on: + + (import* time math) + (def tik (tick 0.25)) + (trace (/ tik 4)) + +Note that if you leave the [time/tick][]'s *tag* in place when you move it into +the [def][] expression, it will keep on running steadily even then. + +[pd]: http://puredata.info/ +[max]: https://cycling74.com/products/max +[vvvv]: https://vvvv.org/ diff --git a/docs/guide/functions.md b/docs/guide/functions.md new file mode 100644 index 0000000..917b8d4 --- /dev/null +++ b/docs/guide/functions.md @@ -0,0 +1,54 @@ +Another builtin that creates a nested scope is [fn][], which is used to +create a *user-defined function*, which can be used to simplify repetitive +code, amongst other things: + + (import* math) + + (def add-and-trace + (fn + (a b) + (trace (+ a b)))) + + (add-and-trace 1 2) + (add-and-trace 3 4) + +Here a *function* `add-and-trace` is defined. When defining a function, first +the names of the parameters have to be given. The function defined here takes +two parameters, `a` and `b`. The last part of the function definition is called +the *function body*. + +A function created using [fn][] can be called just like an operator. When a +function is called, the parameters to the function are defined with the names +given in the definition, and then the function body is executed. The previous +example is equivalent to the following: + + (import* math) + + (def add-and-trace + (fn + (a b) + (trace (+ a b))) + + (do + (let a 1 + b 2) + (trace (+ a b))) + + (do + (let a 3 + b 4) + (trace (+ a b))) + +and the output of both is: + + trace (+ a b): <num= 3> + trace (+ a b): <num= 7> + +In `alv`, functions are first-class values and can be passed around just like +numbers, strings, etc. However it is very common to define a function with a +name, so there is the `defn` shorthand, which combines the `def` and `fn` +builtins into a single expression. Compare this equivalent definition of the +`add-and-trace` function: + + (defn add-and-trace (a b) + (trace (+ a b))) diff --git a/docs/guide/getting-started-guide.md b/docs/guide/getting-started-guide.md new file mode 100644 index 0000000..9ca2f63 --- /dev/null +++ b/docs/guide/getting-started-guide.md @@ -0,0 +1,25 @@ +`alv` ("alive") is a language for creating and changing realtime programs while +they are running continuously. It can be used to create music, visuals or +installations, but by itself creates neither sound nor video. Rather, `alv` is +used together with other tools and synthesizers (for example +[SuperCollider][supercollider] or [Pilot][pilot]). In such an ensemble of +tools, `alive` takes the role of a 'conductor', telling the other tools what to +play when by sending commands to them using a variety of protocols, such as OSC +and MIDI. + +## contents + +1. [installation](installation.html) +2. [hello world](hello-world.html) +3. [working with the copilot](working-with-the-copilot.html) +4. [syntax](syntax.html) +5. [basic types](basic-types.html) +6. [importing operators](importing-operators.html) +7. [defining symbols](symbols.html) +8. [scopes](scopes.html) +9. [functions](functions.html) +10. [evaltime and runtime](evaltime-and-runtime.html) +11. [making sound](making-sound.html) + +[supercollider]: https://supercollider.github.io/ +[pilot]: https://github.com/hundredrabbits/Pilot diff --git a/docs/guide/hello-world.md b/docs/guide/hello-world.md new file mode 100644 index 0000000..7f1517d --- /dev/null +++ b/docs/guide/hello-world.md @@ -0,0 +1,33 @@ +Before getting into all the details of the `alv` language, let's quickly run an +example program to make sure that everything is working correctly: + + ([1]import* time) + ([2]print ([3]every 0.5 "hello world!")) + +Open a text file save this piece of code as `hello.alv`. You can also find this +example program in the repository and the windows binary package. + +As mentioned earlier, there are two different ways to run `alv` programs: using +the copilot GUI, or in the terminal. + +## starting the copilot GUI +On Linux and Mac OS X, you can launch the GUI by executing the `alv-fltk` +command. On Windows, you can double-click `alv-fltk.bat`. This window should +open: + + + +Now open `hello.alv` using `File > Open Script` or the shortcut `^O` +(control-O). The copilot should start printing `hello world` over and over +again in the lower field. + +You can pause and resume execution using the `Run` button or the `^P` shortcut. +To stop the program simply close the window. + +## starting the copilot in the terminal +To run a file in the terminal, invoke the command `alv <path/to/hello.alv>`. If +your system cannot find the `alv` command, check your installation and `PATH`. +On Windows, it is also possible to drag your `alv` file onto `alv.bat`. + +You should now see the text `hello world` being printed over and over again, +twice a second. You can stop the copilot by pressing `^C` (control-C). diff --git a/docs/guide/importing-operators.md b/docs/guide/importing-operators.md new file mode 100644 index 0000000..4321e7a --- /dev/null +++ b/docs/guide/importing-operators.md @@ -0,0 +1,26 @@ +Apart from [trace][], there are only very little builtin operators in `alv` - +you can see all of them in the *builtins* section of the [reference][:/:]. +All of the 'real' functionality of `alv` is grouped into *modules*, that have +to be loaded individually. *Modules* help organize all of the operators so that +it is less overwhelming to look for a concrete feature. It is also possible to +create your own plugins as new modules, which will be covered in another guide +soon. + +Let's try using the [`+` operator][:math/+:] from the [math/][] module. To use +operators from a module, we need to tell `alv` to load it first: We can load +*all* the operators from the [math/][] module into the current scope using the +[import*][] builtin: + + (import* math) + (trace (+ 1 2)) + +prints + + trace (+ 1 2): <num= 3> + +Because it can get a bit confusing when all imported operators are mixed in the +global scope, it is also possible to load the module into its own scope and use +it with a prefix. This is what the [import][] builtin is for: + + (import math) + (trace (math/+ 1 2)) diff --git a/docs/guide/installation.md b/docs/guide/installation.md new file mode 100644 index 0000000..5a192fa --- /dev/null +++ b/docs/guide/installation.md @@ -0,0 +1,53 @@ +`alv` is written in the Lua programming language, and is compatible with both +Lua 5.3 and luajit. + +## unix/linux and mac os +Your distribution should provide you with packages for Lua and Luarocks. On Mac +OS X, both are provided through [homebrew][homebrew]. After installing both of +these, you should be able to start the Lua interpreter from the shell: + + $ lua + Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio + > + +You can exit using `CTRL+C`. If the version you see is not 5.3, double check +your distribution packages or see if it was installed as `lua5.3` or `lua53`. +Similarily, you should be able to run `luarocks`, `luarocks53` or `luarocks5.3`: + + $ luarocks list + + Rocks installed for Lua 5.3 + --------------------------- + +Again, double check your installation or try adding `--lua-version 5.3` if the +displayed version is not 5.3. + +With everything ready to go, you can now install `alv`: + + $ luarocks install alive + +To use the copilot GUI, you will also need the `fltk4lua` package, which requires +installing or building FLTK (also available through homebrew). + + $ luarocks install fltk4lua + +With the `alive` package, two binaries should have been installed on your system: +`alv` and `alv-fltk`. If you do not find these in your `$PATH`, you may need to +apply the exports from `luarocks path` upon login, e.g. in your `.bashrc`. + +## windows +For Windows, a binary package is available from the latest +[github release][:*release*:]. It includes not only the `alv` source code, but +also a compiled version of Lua 5.3 as well as Luarocks and all of `alv`'s +dependencies. + +To use the binary package, simply extract the archive and move the folder +wherever you want. You can now start the `hello.alv` example script by dragging +it onto the `alv.bat` or `alv-fltk.bat` file in the folder. + +If you are going to use the command-line `alv.bat`, it is recommended to add +the directory containing it to `%PATH%`, so that you can use the `alv` command +anywhere on your system. + +[homebrew]: https://brew.sh +[luarocks]: https://github.com/luarocks/luarocks/#installing diff --git a/docs/guide/making-sound.md b/docs/guide/making-sound.md new file mode 100644 index 0000000..1987dac --- /dev/null +++ b/docs/guide/making-sound.md @@ -0,0 +1,58 @@ +As mentioned earlier, `alv` doesn't produce sound by itself. Instead, it is +paired with other tools, and takes the role of a 'Conductor', sending commands +and sequencing other tools. + +For the sake of this guide, we will be controlling [Pilot][pilot], a simple +UDP-controlled synthesizer. You can go ahead and download and open it now. +You should see a small window with a bunch of cryptic symbols and a little +command line at the bottom. To verify that everything is working so far, +try typing in `84c` and hitting enter. This should play a short sound (the note +4C, played by the 8th default synthesizer voice in Pilot). + +To talk to Pilot from `alv`, we will use the [pilot/][] module. Note that for +this module to work, you have to have the `osc` and `luasocket` dependencies +installed. To play the same sound we played by entering `84c` above every 0.5 +seconds, we can use [time/every][] to send a `bang` to [pilot/play][]: + + (import* time) + (import pilot) + (pilot/play (every 0.5) 8 4 'c') + +You can play with the voice, octave and note values a bit. To add a simple +melody, we can use [util/switch][], which will cycle through a list of +parameters when used together with [time/tick][]: + + (import* time util) + (import pilot) + (pilot/play (every 0.5) 8 4 + (switch (tick 0.5) 'c' 'd' 'a' 'f')) + +Now we can have the voice change every other loop as well: + + (import* time util) + (import pilot) + (pilot/play (every 0.5) + (switch (tick 4) 8 9) + 4 (switch (tick 0.5) 'c' 'd' 'a' 'f')) + +To round off the sound a bit, we can turn on Pilot's reverb using +[pilot/effect][]. Add the following somewhere in your file: + + (pilot/effect "REV" 2 8) + +Now it's time to add some rhythm. The kick drum is voice 12 by default, +and we can also add something like a snare on channel 3: + + (pilot/play (every 0.75) + 12 2 'd' 3) + (pilot/play (every 2) + 13 4 'a' 4) + +Note that since we are using multiple individual [time/every][] instances, +the timing of our voices relative to each other is not aligned - each voice +started playing when the file was first saved with it added, and kept the +rhythmn since. By deleting all their tags and re-saving the file, we can force +`alv` to re-instantiate them all at the same time, thereby synchronising +them. + +[pilot]: https://github.com/hundredrabbits/Pilot diff --git a/docs/guide/scopes.md b/docs/guide/scopes.md new file mode 100644 index 0000000..87568ef --- /dev/null +++ b/docs/guide/scopes.md @@ -0,0 +1,82 @@ +Both [import][] and [import*][] are actually shorthands and what they +accomplish can be done using the lower-level builtins [def][], [use][] and +[require][]. Here is how you could replace [import][]: + + #(with import:) + (import math) + (trace (math/+ 1 2)) + + #(with def and require:) + (def math (require "math")) + (trace (math/+ 1 2)) + +[require][] returns a *scope*, which is defined as the symbol `math`. +Then `math/+` is resolved by looking for `+` in this nested scope. Note that +the symbol that the scope is defined as and the name of the module that is +loaded do not have to be the same, you could call the alias whatever you want: + + #(this not possible with import!) + (def fancy-math (require "math")) + (trace (fancy-math/+ 1 2)) + +Most of the time the name of the module makes a handy prefix already, so +[import][] can be used to save a bit of typing and make the code look a bit +cleaner. [import*][], on the other hand, defines every symbol from the imported +module individually. It could be implemented with [use][] like this: + + (use (require "math")) + (trace (+ 1 2)) + +[use][] copies all symbol definitions from the scope it is passed to the +current scope. + +Note that [import][], [import*][], [def][], and [use][] all can take multiple +arguments: + + #(using the shorthands:) + (import* math logic) + (import midi osc) + + #(using require, use and def:) + (use (require "math") (require "logic")) + (def midi (require "midi") + osc (require "osc")) + +It is common to have an [import][] and [import*][] expression at the top of an +`alv` program to load all of the modules that will be used later, but the +modules don't necessarily have to be loaded at the very beginning, as long as +all symbols are defined before they are being used. + +## nested scopes +Once a symbol is defined, it cannot be changed or removed: + + (def a 3) + (def a 4) #(error!) + +It is, however, possible to 'shadow' a symbol by re-defining it in a nested +scope: So far, all symbols we have defined - using `def`, [import][] and +[import*][] - have been defined in the *global scope*, the scope that is active +in the whole `alv` program. The [do][] builtin can be used to create a new +scope and evaluate some expressions in it: + + (import string) + + (def a 1 + b 2) + + (trace (.. "first: " a " " b)) + (do + (def a 3) + (trace (.. "second: " a " " b)) + (trace (.. "third: " a " " b)) + +This example prints the following: + + trace (.. "first: " a " " b): <Value str: first: 1 2> + trace (.. "second: " a " " b): <Value str: second: 3 2> + trace (.. "third: " a " " b): <Value str: third: 1 2> + +As you can see, within a nested scope it is possible to overwrite a definition +from the parent scope. Symbols that are not explicitly redefined in a nested +scope keep their values, and changes in the nested scope do not impact the +parent scope. diff --git a/docs/guide/syntax.md b/docs/guide/syntax.md new file mode 100644 index 0000000..93f1c61 --- /dev/null +++ b/docs/guide/syntax.md @@ -0,0 +1,43 @@ +`alv`'s syntax is very similar to Lisp. Expressions take the form of +parenthesized lists like `(head a b c...)`, where the first element of the list +(`head`) is the name of an operator or function, which defines what the +expression as a whole will do, while the other elements are parameters whose +meaning depends on the `head`. Let's start with a simple operator, [print][]: +[print][] is used simply to print messages to the copilot console. + +## expressions +Elements of an expression have to be separated by whitespace, but any type of +and amount of whitespace is valid: feel free to use spaces, tabs, and newlines +to format code to your liking. The following are all equal and valid examples: + + (print "hello world") + + (+ 1 + 2 + 3) + + (print + "hello world") + + ( print "hello world" ) + +It is however recommended to follow the [clojure style guide][clojure-style] as +much as it does apply to alv. All further examples in this guide will respect +this guideline, so you might just pick it up simply by following this guide. + +## comments +To annotate your code, you can use comments. In `alv`, comments begin with +`#(` and end on a matching `)`. This way you can comment out a complete +expression simply by adding a `#` character in front. + + #(this is a comment) + + #(this is a long, + multi-line comment, + (and it also has nested parentheses). + It ends after this sentence.) + +You can put comments anywhere in your program where whitespace is allowed and +it will simply be ignored by `alv`. + +[clojure-style]: https://github.com/bbatsov/clojure-style-guide diff --git a/docs/guide/working-with-the-copilot.md b/docs/guide/working-with-the-copilot.md new file mode 100644 index 0000000..f40d82d --- /dev/null +++ b/docs/guide/working-with-the-copilot.md @@ -0,0 +1,59 @@ +While it is possible to simply run finished programs as we just did with the +`hello.alv` example, `alv` is a *livecoding language*, which means that it is +designed so that programs can be written interactively while they are already +running. To see how this works, let's re-write the `hello.alv` example +step-by-step. + +First you will need an empty file to start from. Open a new file in your +preferred text editor and save it as `test.alv`. Before adding any code, start +the copilot (see the last page on the two ways of doing that). + + $ alv test.alv + changes to files: test.alv + +You should see a note indicating that `alv` processed the file. The note will +show up in the upper pane labelled `eval` (in the GUI), or colored green (in +the terminal). This marks the message as an *eval-time message*, meaning that +the message was printed as a direct response to the file changing or being +loaded the first time. Other messages that might print to the `eval` section +are things like errors in your program, and one-time debugging messages. + +Now that the copilot is running, whenever you change `test.alv` and save it, the +copilot will reload it and execute your new code. When you are done, you can +stop the copilot at any time by closing the GUI window or pressing `^C` +(control-C) in the terminal. + +Let's start with a simple operator, [print][]: [print][] is used simply to print +messages to the copilot console. Enter the following in your file and save it: + + (print "hello world!") + +As soon as you save the file, you should notice two things happening: + +1. The copilot will print two new lines: + + changes to files: hello.alv + hello world! + + In the first line, it notifies us that the file has changed. In the second + line, you can see the output from the [print][] expression. +2. The copilot will make a small modification to your file. Depending on the + editor you are using, this may either result in you seeing the modification + immediately, or a notice appearing that offers the option to reload the + file. If it is the latter, confirm the notification to accept the changes. + If there is an option to do so, you may want to configure your editor to + always reload the file automatically. + +The code should now look like this: + + ([1]print "hello world") + +The `[1]` that the copilot added to your expression is that expression's `tag`. +In `alv`, every expression has a tag that helps the copilot to identify the +individual expressions as you make changes to your code. The copilot will make +sure that all expressions are tagged by adding missing tags when you save the +file, but you have to watch out not to duplicate a tag when copying and pasting +code. When you duplicate code that already has tags, you can either manually +change the tags to be unique, or simply delete the whole tag (including the +square brackets) and let the copilot generate a new one for you the next time +you save the file. |
