You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/lyte-dsp-quickstart.md
+92-44Lines changed: 92 additions & 44 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,12 @@
1
-
# Lyte DSP Quick Start — Building Audio DSP in Lyte
1
+
# Lyte DSP Quick Start
2
2
3
-
*A practical guide to common DSP building blocks in Audulus Lyte. The examples are meant to be
4
-
small, pasteable, and easy to adapt.*
3
+
*A practical starting guide for building audio tools in Audulus Lyte. The examples are small, easy to paste in, and meant to be changed.*
5
4
6
5
---
7
6
8
7
## The DSP Node
9
8
10
-
When a new Lyte DSP node is created in Audulus, it shows this template:
9
+
When you create a new Lyte DSP node in Audulus, it starts with this template:
11
10
12
11
```lyte
13
12
// Inputs, outputs, `frames`, `sampleRate`, and `storage` are globals.
@@ -23,48 +22,46 @@ process {
23
22
}
24
23
```
25
24
26
-
That template is the whole interface. Everything below builds from it.
25
+
That is the basic shape of a Lyte DSP node. Everything below builds on that pattern.
27
26
28
27
## Terms for New Users
29
28
30
-
These words show up right away in the fresh-node comments:
29
+
These are the main words you will see right away:
31
30
32
-
-`input`, `output`: audio buffers for the current block. `input[i]` means “sample `i`.”
33
-
-`frames`: how many samples are in the current block.
34
-
-`sampleRate`: how many samples happen per second, usually `44100.0` or `48000.0`.
35
-
-`storage`: extra sample memory for things like delay lines.
36
-
-`globals`: variables declared outside `process`. They remember values between blocks.
37
-
-`slice`: a buffer you index with `[...]`.
38
-
-`MAX_FRAMES`: the largest block size Audulus may give the node. Use it for temporary arrays when needed.
31
+
-`input`, `output`: the audio coming in and going out for the current block. `input[i]` means “sample number `i`”
32
+
-`frames`: how many samples are in this block
33
+
-`sampleRate`: how many samples happen each second, usually `44100.0` or `48000.0`
34
+
-`storage`: extra memory for things like delay lines
35
+
-`globals`: variables declared outside `process`. They keep their value from one block to the next
36
+
-`slice`: a buffer you read with `[...]`
37
+
-`MAX_FRAMES`: the biggest block size Audulus may send to the node. Use it when you need a temporary array
39
38
40
-
You do not declare `input`, `output`, `frames`, `sampleRate`, or `storage` yourself.
41
-
Audulus provides them.
39
+
You do not create `frames`, `sampleRate`, or `storage` yourself. Audulus gives them to the node automatically.
42
40
43
41
| Global | Type | Description |
44
42
|---|---|---|
45
-
|`input`, `output`|`[f32]`| Default port slices. Additional named ports are added in the Inspector. |
43
+
|`input`, `output`|`[f32]`| Default port slices. Add ports as needed above the code viewer in the Inspector. |
46
44
|`frames`|`i32`| Number of samples in this processing block. |
47
45
|`sampleRate`|`f32`| Current sample rate, e.g. `44100.0`. |
48
-
|`storage`|`[f32]`| Pre-allocated buffer for delay lines etc., sized in the Inspector. |
46
+
|`storage`|`[f32]`| Pre-allocated buffer for delay lines etc., set the size in the Inspector. |
49
47
|`MAX_FRAMES`|`i32`| Maximum possible block size — use to declare stack arrays. |
50
48
51
-
**`init {}`** runs once when the node loads. Use it for setup.
49
+
**`init {}`** runs once when the node loads. Use it for setup values.
52
50
53
-
**`process { ... }`** runs once per block. Loop over `for i in 0 .. frames` and read or
54
-
write the buffers.
51
+
**`process { ... }`** runs once per block. Most audio work happens here inside `for i in 0 .. frames`.
55
52
56
53
**Global `var`** is for state that should survive between blocks. Globals start at zero.
57
54
58
-
**Indexing is zero-based.** In `for i in 0 .. frames`, valid indices are `0` through
59
-
`frames - 1`. Use `freq[i]` for normal per-sample processing. Use `freq[0]` only when you
60
-
intentionally want the first sample in the block.
55
+
**Indexing starts at zero.** In `for i in 0 .. frames`, valid indices are `0` through
56
+
`frames - 1`. Use `freq[i]` for normal sample-by-sample processing. Use `freq[0]` only when you
57
+
intentionally want one control value for the whole block. If you are coming from Lua, this is different because Lua starts at `1`.
61
58
62
59
---
63
60
64
-
## A Note on`sampleRate`
61
+
## Using`sampleRate`
65
62
66
-
`sampleRate`now behaves like a normal float. The usual pattern is to compute
67
-
`1.0 / sampleRate` once in `init` and multiply by that inside `process`.
63
+
`sampleRate`is a float. A common pattern is to compute`1.0 / sampleRate` once in
64
+
`init` and then multiply by that inside `process`.
68
65
69
66
```lyte
70
67
var inv_sr: f32
@@ -74,13 +71,13 @@ init {
74
71
}
75
72
```
76
73
77
-
This also avoids doing a division every sample.
74
+
This also avoids doing a division on every sample.
78
75
79
76
---
80
77
81
78
## Do's and Don'ts
82
79
83
-
These habits gave the most reliable results in the current Audulus build.
80
+
These are good default habits for writing Lyte in Audulus.
84
81
85
82
**Do:**
86
83
- Prefer `f32` almost everywhere in DSP code.
@@ -90,13 +87,60 @@ These habits gave the most reliable results in the current Audulus build.
90
87
- Read a port sample into a scratch `f32` first if the compiler gets ambiguous about expressions like `input[i] * inv_sr`.
91
88
- Keep slice index proofs inline near `storage[...]` accesses when the safety checker complains.
92
89
- Use `freq[i]`, `cutoff[i]`, and similar forms for normal per-sample processing. Use `freq[0]` only when you intentionally want one control value for the whole block.
90
+
- Move expensive math out of the sample loop when true audio-rate updates are not needed.
91
+
- Try block-rate control first when it sounds good enough. It is usually simpler and cheaper.
93
92
94
93
**Don't:**
95
-
- Don't assume Expr-node conveniences exist in Lyte. For example, `pi`is not built in here.
96
-
- Don't use `sinf`, `cosf`, or other suffixed math names in Audulus Lyte.
94
+
- Don't assume every Expr-node feature maps over exactly. Lyte does have built-in trig/math functions and stdlib helpers like `clamp(x, lo, hi)` and `mix(a, b, t)`, but constants like `pi`are still not built in.
95
+
- Don't use `sinf`, `cosf`, or other suffixed math names in Audulus Lyte. Use unsuffixed names like `sin`, `cos`, `tan`, `atan2`, `sqrt`, `clamp`, and `mix`.
97
96
- Don't rely on helper functions to prove slice indices are safe; the checker usually wants the bounds checks inline.
98
97
- Don't use `assume` in node code — it is only allowed in the standard library or prelude.
99
98
- Don't assume examples from the standalone Lyte repo will drop into Audulus unchanged.
99
+
- Don't rely on block-form inline `if` expressions in assignments such as `let x = if ...` or `output[i] = if ...`. In Lyte those can trigger confusing parser errors. Use a normal assignment first, then a plain `if` block.
100
+
101
+
---
102
+
103
+
## Debug Printing
104
+
105
+
`println` is useful for simple debugging. For numbers, write into a
106
+
small text buffer first with `itoa` or `ftoa`, then print the buffer.
107
+
108
+
### Hello World
109
+
110
+
```lyte
111
+
init {
112
+
println("hello world")
113
+
}
114
+
115
+
process {
116
+
for i in 0 .. frames {
117
+
output[i] = input[i]
118
+
}
119
+
}
120
+
```
121
+
122
+
### Block Counter
123
+
124
+
This prints how many times `process` has run so far.
125
+
126
+
```lyte
127
+
var calls: i32
128
+
129
+
init {}
130
+
131
+
process {
132
+
var buf: [i8; 32]
133
+
itoa(buf, calls)
134
+
println(buf)
135
+
calls = calls + 1
136
+
137
+
for i in 0 .. frames {
138
+
output[i] = input[i]
139
+
}
140
+
}
141
+
```
142
+
143
+
Use debug printing sparingly. Printing every block gets noisy fast.
100
144
101
145
---
102
146
@@ -141,8 +185,7 @@ process {
141
185
}
142
186
```
143
187
144
-
`phase` remembers where the oscillator is in its cycle. `hz` is a scratch `f32` used to
145
-
keep the compiler happy when doing math with `input[i]`.
188
+
`phase` remembers where the oscillator is in its cycle. `hz` is just a temporary variable that makes the math clearer and avoids compiler confusion around `input[i]`.
146
189
147
190
---
148
191
@@ -169,7 +212,7 @@ process {
169
212
}
170
213
```
171
214
172
-
Use `let` for one-time values inside the loop. Use `var` for values that change.
215
+
Use `let` for a value you set once and do not change. Use `var` for a value that changes.
173
216
174
217
---
175
218
## 1. Timer
@@ -242,8 +285,8 @@ process {
242
285
`sync` is a rising-edge trigger. `hz * inv_sr` is the fraction of one cycle that passes
243
286
per sample.
244
287
245
-
> 🔄 *Audulus users: The built-in Audulus Phasor outputs `0` to `2π`. This one uses `0` to
246
-
> `1`, which is often simpler in DSP code.*
288
+
Note for Audulus users: the built-in Audulus Phasor outputs `0` to `2π`. This example uses `0` to
289
+
`1`, which is often easier to work with in Lyte code.
0 commit comments