Skip to content

Commit c9a9331

Browse files
authored
Merge pull request #14 from jersmi7/update-docs-v2
Update docs: quickstart and tutorial for Audulus users
2 parents 97090f6 + 86ca7a0 commit c9a9331

2 files changed

Lines changed: 255 additions & 103 deletions

File tree

docs/lyte-dsp-quickstart.md

Lines changed: 92 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
# Lyte DSP Quick Start — Building Audio DSP in Lyte
1+
# Lyte DSP Quick Start
22

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.*
54

65
---
76

87
## The DSP Node
98

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:
1110

1211
```lyte
1312
// Inputs, outputs, `frames`, `sampleRate`, and `storage` are globals.
@@ -23,48 +22,46 @@ process {
2322
}
2423
```
2524

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.
2726

2827
## Terms for New Users
2928

30-
These words show up right away in the fresh-node comments:
29+
These are the main words you will see right away:
3130

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
3938

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.
4240

4341
| Global | Type | Description |
4442
|---|---|---|
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. |
4644
| `frames` | `i32` | Number of samples in this processing block. |
4745
| `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. |
4947
| `MAX_FRAMES` | `i32` | Maximum possible block size — use to declare stack arrays. |
5048

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.
5250

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`.
5552

5653
**Global `var`** is for state that should survive between blocks. Globals start at zero.
5754

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`.
6158

6259
---
6360

64-
## A Note on `sampleRate`
61+
## Using `sampleRate`
6562

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`.
6865

6966
```lyte
7067
var inv_sr: f32
@@ -74,13 +71,13 @@ init {
7471
}
7572
```
7673

77-
This also avoids doing a division every sample.
74+
This also avoids doing a division on every sample.
7875

7976
---
8077

8178
## Do's and Don'ts
8279

83-
These habits gave the most reliable results in the current Audulus build.
80+
These are good default habits for writing Lyte in Audulus.
8481

8582
**Do:**
8683
- Prefer `f32` almost everywhere in DSP code.
@@ -90,13 +87,60 @@ These habits gave the most reliable results in the current Audulus build.
9087
- Read a port sample into a scratch `f32` first if the compiler gets ambiguous about expressions like `input[i] * inv_sr`.
9188
- Keep slice index proofs inline near `storage[...]` accesses when the safety checker complains.
9289
- 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.
9392

9493
**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`.
9796
- Don't rely on helper functions to prove slice indices are safe; the checker usually wants the bounds checks inline.
9897
- Don't use `assume` in node code — it is only allowed in the standard library or prelude.
9998
- 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.
100144

101145
---
102146

@@ -141,8 +185,7 @@ process {
141185
}
142186
```
143187

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]`.
146189

147190
---
148191

@@ -169,7 +212,7 @@ process {
169212
}
170213
```
171214

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.
173216

174217
---
175218
## 1. Timer
@@ -242,8 +285,8 @@ process {
242285
`sync` is a rising-edge trigger. `hz * inv_sr` is the fraction of one cycle that passes
243286
per sample.
244287

245-
> 🔄 *Audulus users: The built-in Audulus Phasor outputs `0` to ``. 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 ``. This example uses `0` to
289+
`1`, which is often easier to work with in Lyte code.
247290

248291
### Clock
249292

@@ -363,12 +406,15 @@ output[i] = phase * 2.0 - 1.0
363406
// triangle: fold the sawtooth
364407
output[i] = abs(phase * 2.0 - 1.0) * 2.0 - 1.0
365408
366-
// square: if/else is an expression in Lyte
409+
// square
367410
var pw: f32 // global pulse width; 0.5 = 50% duty cycle
368-
output[i] = if phase < pw { 1.0 } else { -1.0 }
411+
output[i] = -1.0
412+
if phase < pw {
413+
output[i] = 1.0
414+
}
369415
```
370416

371-
`if/else` can return a value directly, so there is no ternary operator to learn.
417+
This plain assignment style is a safe habit in Lyte and avoids parser trouble from inline `if` expressions.
372418

373419
---
374420
## 4. Sample and Hold
@@ -526,7 +572,10 @@ process {
526572
for i in 0 .. frames {
527573
let x = input[i]
528574
let fc = cutoff[i]
529-
let res = if q[i] < 0.001 { 0.001 } else { q[i] }
575+
var res = q[i]
576+
if res < 0.001 {
577+
res = 0.001
578+
}
530579
let w0 = 2.0 * 3.14159265 * fc * inv_sr
531580
let alpha = sin(w0) / (2.0 * res)
532581
let cs = cos(w0)
@@ -549,7 +598,7 @@ process {
549598
```
550599

551600
This is direct form I using plain `f32` globals for the saved state. This flatter version
552-
is the one verified to behave correctly in the current Audulus build.
601+
is the one used in this guide.
553602

554603
### DC Blocker
555604

@@ -619,10 +668,9 @@ process {
619668
}
620669
621670
let read_pos_0 = write as f32 - delay
622-
let read_pos = if read_pos_0 < 0.0 {
623-
read_pos_0 + len as f32
624-
} else {
625-
read_pos_0
671+
var read_pos = read_pos_0
672+
if read_pos < 0.0 {
673+
read_pos = read_pos + len as f32
626674
}
627675
628676
let i0 = floor(read_pos) as i32

0 commit comments

Comments
 (0)