tutticode 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +11 -0
  2. package/README.zh-CN.md +10 -0
  3. package/dist/skills/tutticode/SKILL.md +11 -10
  4. package/dist/skills/tutticode/references/agent-loop.md +15 -0
  5. package/dist/skills/tutticode/references/automation.md +168 -0
  6. package/dist/skills/tutticode/references/devices/compressor.md +2 -0
  7. package/dist/skills/tutticode/references/devices/filter.md +2 -0
  8. package/dist/skills/tutticode/references/devices/gate.md +2 -0
  9. package/dist/skills/tutticode/references/devices/glue.md +2 -0
  10. package/dist/skills/tutticode/references/devices/multibanddynamics.md +2 -0
  11. package/dist/skills/tutticode/references/devices.md +2 -2
  12. package/dist/skills/tutticode/references/effect-controls.md +8 -0
  13. package/dist/skills/tutticode/references/first-sounds.md +2 -0
  14. package/dist/skills/tutticode/references/for-agents.md +26 -0
  15. package/dist/skills/tutticode/references/functions.md +67 -5
  16. package/dist/skills/tutticode/references/harmony-and-scales.md +3 -0
  17. package/dist/skills/tutticode/references/language-reference.md +362 -0
  18. package/dist/skills/tutticode/references/melodies-with-do-re-mi.md +41 -5
  19. package/dist/skills/tutticode/references/mixing-groups.md +7 -4
  20. package/dist/skills/tutticode/references/mixing.md +48 -6
  21. package/dist/skills/tutticode/references/muse.md +7 -0
  22. package/dist/skills/tutticode/references/sounds.md +26 -0
  23. package/dist/skills/tutticode/references/tuttidaw-extensions.md +13 -6
  24. package/dist/skills/tutticode-sound-for-media/SKILL.md +11 -1
  25. package/dist/skills/tutticode-sound-for-media/references/music-recipes.md +30 -6
  26. package/dist/tutticode.cjs +2 -2
  27. package/package.json +1 -1
package/README.md CHANGED
@@ -73,6 +73,17 @@ clipping"). `--lufs` applies one static gain; when the true-peak ceiling
73
73
  cannot be met the render fails with `clip`, unless you add `--limit`: a
74
74
  true-peak limiter on the mix (not the stems), reported as `limitDb`.
75
75
 
76
+ A program can be a whole song (up to 128 bars): each track's sound once, each
77
+ section's content in one block, the order in a form. `render` renders one
78
+ pass of the form by default.
79
+
80
+ ```js
81
+ track("bass").inst("Drift:Deep Bass").mix({vol: -3})
82
+ section("verse", 8, { drums: s("bd ~ sd ~, hh*8"), bass: note("<c2 eb2>*4") })
83
+ section("chorus", 8, { drums: s("bd sd bd sd, hh*16"), bass: note("<c2 ab1>*8") })
84
+ form("verse chorus verse chorus")
85
+ ```
86
+
76
87
  `--json` prints the render report: length, bars, tempo, loudness and true
77
88
  peak, the gain applied, per-voice levels, the bar and beat grid (seconds, for
78
89
  syncing cuts), the check report, the analysis, the `editUrl` and the written
package/README.zh-CN.md CHANGED
@@ -63,6 +63,16 @@ cat song.tc | tutticode check - # 从 stdin 读程序
63
63
  偷偷加限制器;需要时加 `--limit`:对混音(不含分轨)加 true peak 限制器,
64
64
  报告里的 `limitDb` 是它最多压下的量。
65
65
 
66
+ 程序可以是一整首歌(最长 128 小节):每条轨道的音色写一次,每个段落的内容写在一个块里,
67
+ 顺序写在 form 里。`render` 默认渲染整个曲式一遍。
68
+
69
+ ```js
70
+ track("bass").inst("Drift:Deep Bass").mix({vol: -3})
71
+ section("verse", 8, { drums: s("bd ~ sd ~, hh*8"), bass: note("<c2 eb2>*4") })
72
+ section("chorus", 8, { drums: s("bd sd bd sd, hh*16"), bass: note("<c2 ab1>*8") })
73
+ form("verse chorus verse chorus")
74
+ ```
75
+
66
76
  `--json` 输出渲染报告:时长、小节数、速度、响度和 true peak、施加的增益、每个
67
77
  声部的电平、小节和拍的时间点(秒,用来对齐剪辑点)、检查报告、分析结果、
68
78
  `editUrl` 和写出的文件路径。永远不会把音频打印到终端。
@@ -39,10 +39,10 @@ _lead: n("0 2 4 7").scale("C:minor") // a leading _ mutes a voice
39
39
  `.range(a, b)` `.slow(n)` `.segment(n)`.
40
40
  - TuttiDAW only: `.inst("Device:Preset", {key: value})` instrument,
41
41
  `.midifx(...)` before it, `.fx(...)` after it, in call order. Params use the
42
- device's keys and units (`"4s"`, `"-6dB"`, `"1/16"`); constants only for now.
43
- Mixing: `bus("verb").fx("Reverb")` + `.send("verb", -12)`, `.mix({vol, pan,
44
- mute})`, `master.fx("Limiter")`, `group("drums")` + `.group("drums")`.
45
- `toggle(1)` is an inline switch; `note("do re mi")` = `note("c d e")`.
42
+ device's keys and units (`"4s"`, `"-6dB"`, `"1/16"`); a pattern automates.
43
+ Mixing: voices sharing a reverb/delay get one `bus("verb").fx("Reverb", {dryWetBalance: 1})` and a `.send("verb", -12)` each,
44
+ not an `.fx`/`.room` per voice; `.mix({vol, pan, mute})`; finish a piece with `master.fx("Limiter")`; `group("drums")` + `.group("drums")`.
45
+ `toggle(1)` is an inline switch; `note("do re mi")` = `note("c d e")`; `ensembleSet({reverb: {revSize: 100}})` sets the Ensemble Sets' buses/master.
46
46
 
47
47
  ## Top mistakes
48
48
 
@@ -55,8 +55,8 @@ _lead: n("0 2 4 7").scale("C:minor") // a leading _ mutes a voice
55
55
  `"A:minor:pentatonic"`. `"C:harmonic minor"` is two steps.
56
56
  4. **Device calls inside callbacks** fail: `every(2, x => x.fx(...))` is an
57
57
  error; a voice has one chain. Put `.inst/.fx/.midifx` on the voice.
58
- 5. **Patterned device params** (`{cutoff: "<300 900>"}`) are not applied
59
- yet; use Strudel controls (`.lpf("<300 900>")`) for movement.
58
+ 5. **Automation**: `.mix({vol: ramp(-60, 0, 4)})` fades over 4 bars (`.once()`: no loop); `env("0:200 2:4000~.5 _4:0")`
59
+ is points pos:value (`~k` curve, `_` step); patterns `"<300 900>"` step. On a bus/group/master: Arrangement lanes.
60
60
  6. **One `.inst` per voice**; a second is an error. Use another voice.
61
61
  7. **Single vs double quotes**: double quotes are mini-notation; single
62
62
  quotes are plain strings (`register('name', ...)`, `.scale('C minor')`).
@@ -131,18 +131,19 @@ Read only what the task needs:
131
131
  - `references/mini-notation.md`: Mini-notation. The rhythm language inside the quotes - sequences, rests, groups, alternation, polymeter, Euclidean rhythms and chance.
132
132
  - `references/time-and-structure.md`: Time and structure. Functions that speed up, shift, reverse, repeat and combine patterns, and arrange them into sections.
133
133
  - `references/harmony-and-scales.md`: Harmony and scales. Note names and octaves, scale degrees, transposition, chord symbols, voicings and arpeggios.
134
- - `references/melodies-with-do-re-mi.md`: Melodies with do re mi. Write notes in solfège (do re mi fa sol la si) wherever a note name goes; strudel.cc gets letters.
134
+ - `references/melodies-with-do-re-mi.md`: Melodies with do re mi. Write notes in solfège (do re mi fa sol la si) wherever a note name goes; Export to Strudel gives letters.
135
135
  - `references/sounds.md`: Sounds. Drum kits and banks, synth waveforms, General MIDI instrument names, Ensemble sounds and your own samples.
136
136
  - `references/effect-controls.md`: Effect controls. Filters, reverb, delay, distortion, panning, envelopes and synth controls - and moving them with signals.
137
137
  - `references/sample-operations.md`: Sample operations. Play part of a sample, change its speed, cut it into slices, fit it to the tempo and choke it.
138
138
  - `references/tuttidaw-extensions.md`: TuttiDAW extensions. Load any TuttiDAW instrument, MIDI effect or audio effect with presets and parameters; voices, groups and Open in TuttiDAW.
139
- - `references/mixing.md`: Mixing: buses and master. Shared reverbs and delays on buses, sends from the voices, each voice's fader and pan, and the program's master chain - the same tracks Open in TuttiDAW makes.
139
+ - `references/automation.md`: Automation. Fades with ramp(), points and curves with env(), patterns and signals - device parameters, faders and sends that move over time, as the same automation Open in TuttiDAW shows.
140
+ - `references/mixing.md`: Mixing: buses and master. Shared reverbs and delays on buses, sends from the voices, each voice's fader and pan, sidechain ducking, and the program's master chain - the same tracks Open in TuttiDAW makes.
140
141
  - `references/mixing-groups.md`: Mixing: groups. Put voices in a group with its own effects, fader and sends - a drum bus inside the program, the same group track Open in TuttiDAW makes.
141
142
  - `references/javascript.md`: JavaScript in code. Variables, functions, loops and register - and the sandbox, determinism and limits that keep shared code safe.
142
143
  - `references/muse.md`: Muse on /TuttiCode. Ask TuttiDAW's AI assistant to write, explain, fix or restyle your program, from text, a Strudel tune, a screenshot or a reference track.
143
144
  - `references/for-agents.md`: TuttiCode for agents. Let Claude Code, Copilot, Codex or any MCP client write TuttiCode and render it to audio files in your own browser, with the tutticode CLI, MCP server and skills.
144
- - `references/functions.md`: every function, method and signal, with signature, description and example (generated from the engine).
145
+ - `references/functions.md`: every function, method and signal, with signature, description and example; `references/language-reference.md`: the full EBNF grammar, typed signatures, control aliases (both generated from the engine).
145
146
  - `references/devices.md`: every instrument and effect for `.inst/.fx/.midifx`, with `references/devices/<id>.md` per device: param keys, ranges, units, presets (generated from the device catalogue).
146
147
 
147
- Language: PATTERN LANGUAGE 1.3.0, engine 17.
148
+ Language: PATTERN LANGUAGE 1.9.0, engine 23.
148
149
  Human guide: https://tuttidaw.com/code/learn; agents: https://tuttidaw.com/code/learn/for-agents
@@ -128,6 +128,21 @@ or mute (`_`) the voices you are not listening to.
128
128
  `jazz-swing` and others); a render without a master chain sits a few LU
129
129
  under a style's mastered loudness, so match its spectrum first.
130
130
 
131
+ A program mixes itself: shared effects on a bus the voices send to, levels
132
+ with `.mix`, and the program's own master chain, which every render goes
133
+ through. A limiter there lets a `lufs` target fit under the ceiling
134
+ without `limit: true`:
135
+
136
+ ```js
137
+ bus("verb").fx("Reverb", {dryWetBalance: 1})
138
+ master.fx("Limiter", {gainDb: 3, ceiling: -3})
139
+ drums: s("bd*4, ~ cp, hh*8").bank("tr909").mix({vol: -3})
140
+ keys: chord("<Cm7 Fm7>").voicing().inst("Electric").send("verb", -10)
141
+ ```
142
+
143
+ `group("drums")` + `.group("drums")` gives several voices one fader and
144
+ chain. `note("do re mi")` is `note("c d e")` (fixed do).
145
+
131
146
  ## Comparing
132
147
 
133
148
  `code_compare` returns `deltas[]` (`metric`, `current`, `target`,
@@ -0,0 +1,168 @@
1
+ # Automation
2
+
3
+ Fades with ramp(), points and curves with env(), patterns and signals - device parameters, faders and sends that move over time, as the same automation Open in TuttiDAW shows.
4
+
5
+ A device parameter does not have to stay put. Give it a **pattern** and it
6
+ changes from cycle to cycle; give it a **signal** and it sweeps. This is
7
+ automation, written in code: "Open in TuttiDAW" shows it as an envelope on
8
+ the voice's clip, and the voice plays it the same way on /code.
9
+
10
+ | Value | What the device gets |
11
+ |---|---|
12
+ | `0.4`, `"4s"`, `slider(0.4)` | a setting that stays |
13
+ | `ramp(-60, 0, 4)` | a straight line from -60 to 0 over 4 bars, then again |
14
+ | `env("0:200 2:4000~.5 4:200")` | points joined by lines and curves |
15
+ | `"<0.1 0.6>"`, `"0 0.5 1 0.5"` | a step at each event: the value holds until the next one |
16
+ | `sine.range(300, 3000)` | a smooth sweep, sampled 16 times per cycle |
17
+
18
+ Like `.inst`, `.fx` and `.midifx` themselves, this is TuttiDAW-only.
19
+
20
+ ## Fades: ramp
21
+
22
+ `ramp(from, to, bars)` is a straight line from one value to another over
23
+ a number of bars. A fade-in on the kick's fader, over four bars:
24
+
25
+ ```js
26
+ kick: s("bd*4").mix({vol: ramp(-60, 0, 4)})
27
+ ```
28
+
29
+ The ramp then starts again, like a pattern loops. Add `.once()` and it
30
+ plays once from the start of the program, then holds its last value:
31
+
32
+ ```js
33
+ pad: note("<c3 eb3 g3 bb3>").s("sawtooth").release(0.3)
34
+ .mix({vol: ramp(-60, -6, 8).once()})
35
+ ```
36
+
37
+ The program's loop grows to cover a ramp or an envelope (here 8 bars), so
38
+ on /code you hear all of it.
39
+
40
+ ## Points, lines and curves: env
41
+
42
+ `env("...")` draws any shape as points `position:value`, joined by
43
+ straight lines. Positions are in bars (`1.5`, `3/4`); values are in the
44
+ parameter's unit, units allowed (`"-6dB"`, `"2kHz"`):
45
+
46
+ ```js
47
+ pad: note("c3").s("sawtooth")
48
+ .fx("Auto Filter", {frequency: env("0:200 2:4000~.5 4:200")})
49
+ ```
50
+
51
+ | Written | Means |
52
+ |---|---|
53
+ | `2:4000` | at bar 2 the value is 4000; a straight line leads there |
54
+ | `2:4000~.5` | the segment ending there bends (`-1..1`; positive stays near the start longer, like the studio's curve) |
55
+ | `_2:4000` | hold the value before until bar 2, then jump |
56
+ | `2:0 2:1` | two points at one position: a jump |
57
+
58
+ The envelope loops over its last position. A second argument makes the loop
59
+ longer: `env("0:0 2:1", 4)` rises for two bars and holds for two. Before
60
+ its first point it holds the first value.
61
+
62
+ ## The exact form
63
+
64
+ The studio can also store a curve drawn as a Bezier (Live's curve handles).
65
+ Code writes it as a list of points, each `[position, value]` with an
66
+ optional segment: `{curve: k}`, `{curve: [x1, y1, x2, y2]}` or `"hold"`:
67
+
68
+ ```js
69
+ lead: note("c4 eb4 g4").s("sawtooth")
70
+ .mix({pan: env([[0, -1], [2, 1, {curve: [0.2, 0, 0.8, 1]}], [4, -1, "hold"]])})
71
+ ```
72
+
73
+ You rarely write this by hand: "Copy as TuttiCode code" in the studio
74
+ writes the shortest form that keeps the automation exactly - a `ramp`,
75
+ an `env` string, or this list.
76
+
77
+ ## A wetter reverb every other bar
78
+
79
+ `"<0.1 0.6>"` is 0.1 in the first cycle and 0.6 in the second:
80
+
81
+ ```js
82
+ pad: note("<c3 eb3 g3 bb3>").s("sawtooth").release(0.3)
83
+ .fx("Reverb", {dryWetBalance: "<0.1 0.6>", decayTime: "3s"})
84
+ ```
85
+
86
+ `decayTime` is a constant, so it stays at 3 seconds. Several parameters
87
+ of one device can move at once, each with its own pattern.
88
+
89
+ ## A filter sweep
90
+
91
+ A signal moves continuously. `sine.range(300, 3000)` goes from 300 Hz up to
92
+ 3000 Hz and back once a cycle; `.slow(4)` stretches that over four:
93
+
94
+ ```js
95
+ bass: note("c2*8").s("sawtooth")
96
+ .fx("Auto Filter", {frequency: sine.range(300, 3000).slow(4)})
97
+ ```
98
+
99
+ The program then loops after four cycles, like any pattern that changes
100
+ over four.
101
+
102
+ ## Units, instruments and MIDI effects
103
+
104
+ Patterns take the same values as constants, in the parameter's unit:
105
+ strings with a unit convert (`"<-6dB 0dB>"`, `"<1s 4s>"`). Instrument
106
+ parameters automate too:
107
+
108
+ ```js
109
+ lead: note("c4 eb4 g4 bb4").inst("Drift", {cutoff: "<400 1200 2400 800>"})
110
+ .fx("Utility", {gainDb: "<-6dB 0dB>"})
111
+ ```
112
+
113
+ A choice parameter steps between its names. Arpeggiator's rate names
114
+ (`1/16`, `1/8`) read as names inside a pattern, not as a division:
115
+
116
+ ```js
117
+ arp: note("[c3,e3,g3]").midifx("Arpeggiator", {style: "<Up Down>", rate: "<1/16 1/8>"})
118
+ ```
119
+
120
+ Names with spaces (`"Up & Down"`) cannot be steps of a pattern: use their
121
+ index. A percentage (`"30%"`) works as a constant only, because `%` means
122
+ something else in mini-notation - write `0.3` in a pattern.
123
+
124
+ ## Buses, groups and the master
125
+
126
+ A bus, a group and the master move the same way: their `.mix({vol, pan})`,
127
+ a group's `.send(bus, level)` and their devices' parameters take patterns
128
+ and signals too.
129
+
130
+ ```js
131
+ bus("verb").fx("Reverb", {dryWetBalance: 1, decayTime: "<2s 6s>"})
132
+ .mix({vol: sine.range(-18, -6).slow(4)})
133
+ group("drums").fx("Drum Buss", {drive: "<0 20>"}).send("verb", "<-30 -12>")
134
+ master.mix({vol: "<0 -3>"})
135
+
136
+ kick: s("bd*4").group("drums")
137
+ pad: note("<c3 eb3>").s("sawtooth").release(0.3).send("verb", -6)
138
+ ```
139
+
140
+ These tracks have no clip of their own, so "Open in TuttiDAW" writes their
141
+ automation into the **Arrangement**: on the return track, the drums group
142
+ and the program's group (the master), one copy per loop across the span
143
+ the voice clips cover. Lengthen the clips and evaluate again: the
144
+ automation follows. The points the code wrote are the code's - edit one
145
+ and that bus, group or master is detached (Re-attach brings the code back).
146
+ Points you draw before or after that span are yours: the code never
147
+ touches them.
148
+
149
+ ## What cannot move
150
+
151
+ - **Static settings** take a constant: an Ensemble part's `voice` or filter
152
+ type. A pattern there is an error that lists the parameters that can move.
153
+ - A pattern of names for the device itself (`.fx("<Reverb Delay>")`) is an
154
+ error: a voice has one chain.
155
+
156
+ ## In the studio
157
+
158
+ The envelope belongs to the code, like the voice's other values: an
159
+ evaluate rewrites it, and the device shows the pattern's first value.
160
+ `ramp` and `env` arrive as real points with their curves, editable like
161
+ any envelope. One as long as the voice's clip loops with the clip; a
162
+ longer or shorter one is an unlinked clip envelope with its own loop, and
163
+ a `.once()` one an unlinked envelope with its loop switched off. Edit
164
+ the envelope by hand and the voice is detached from the code (Re-attach
165
+ brings the code back). An envelope holds at most 1024 points; a pattern
166
+ that changes more often is held on a coarser grid, with a warning.
167
+
168
+ Source: https://tuttidaw.com/code/learn/automation
@@ -35,6 +35,8 @@ Accepted names: `compressor`.
35
35
  | `sideListen` | S/C Listen | `true` / `false` |
36
36
  | `lookahead` | Lookahead | `0 ms` `1 ms` `10 ms` |
37
37
 
38
+ Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
39
+
38
40
  ## Factory presets
39
41
 
40
42
  Acoustic · Acoustic Kick · Gentle Glue · Vocal Leveler
@@ -50,6 +50,8 @@ Accepted names: `autofilter`, `filter`.
50
50
  | `envSahRate` | Env S&H Rate | -6 .. 0 |
51
51
  | `djControl` | Control | -1 .. 1 |
52
52
 
53
+ Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
54
+
53
55
  ## Factory presets
54
56
 
55
57
  Auto Filter Sweep · Auto Wah · Low Cut
@@ -31,6 +31,8 @@ Accepted names: `gate`.
31
31
  | `lookahead` | Lookahead | `0 ms` `1 ms` `10 ms` |
32
32
  | `scFilterMode` | SC EQ Mode | `Low Shelf` `Bell` `High Shelf` `Low Pass` `Band Pass` `High Pass` |
33
33
 
34
+ Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
35
+
34
36
  ## Factory presets
35
37
 
36
38
  Drum Gate · Flipside · Soft Cleanup
@@ -31,6 +31,8 @@ Accepted names: `gluecompressor`, `glue`.
31
31
  | `scFilterOn` | S/C EQ On | `true` / `false` |
32
32
  | `scOn` | S/C On | `true` / `false` |
33
33
 
34
+ Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
35
+
34
36
  ## Factory presets
35
37
 
36
38
  Drum Buss Glue · Gentle Mix Bus · Parallel Crush
@@ -57,6 +57,8 @@ Accepted names: `multibanddynamics`.
57
57
  | `bands.2.attackMs` | High Attack | 0.1 .. 5000 ms (log) |
58
58
  | `bands.2.releaseMs` | High Release | 0.1 .. 5000 ms (log) |
59
59
 
60
+ Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
61
+
60
62
  ## Factory presets
61
63
 
62
64
  De-Mud Upward Low · Gentle Multiband Glue · Multiband Expander
@@ -77,8 +77,8 @@ ranges and units.
77
77
  | [Drum Buss](devices/drumbuss.md) | available | 3 | 11 |
78
78
  | [Dynamic Tube](devices/dynamic-tube.md) | available | 2 | 8 |
79
79
  | [Echo](devices/echo.md) | tested end to end | 5 | 38 |
80
- | Ensemble Bus | not available in code yet: an Ensemble Set's buses come in a later version | 0 | 7 |
81
- | Ensemble Master | not available in code yet: an Ensemble Set's master comes in a later version | 0 | 25 |
80
+ | Ensemble Bus | not available in code: an Ensemble Set's buses are set with ensembleSet({...}) | 0 | 9 |
81
+ | Ensemble Master | not available in code: an Ensemble Set's master is set with ensembleSet({...}) | 0 | 26 |
82
82
  | Envelope Follower | not available in code yet: modulators need mapping targets, which come in a later version | 5 | 8 |
83
83
  | [EQ Eight](devices/eqeight.md) | tested end to end | 3 | 51 |
84
84
  | [EQ Three](devices/eqthree.md) | available | 2 | 9 |
@@ -41,6 +41,10 @@ s("~ cp ~ cp").room(0.6).size(4)
41
41
  note("<c4 eb4> ~ ~ ~").s("triangle").delay(0.5).delaytime(3/16).delayfeedback(0.55)
42
42
  ```
43
43
 
44
+ `.room` and `.delay` give each voice its own reverb or echo. For one reverb
45
+ shared by several voices, so they sound like one room, use a bus and send to
46
+ it: see [Mixing](mixing.md).
47
+
44
48
  ## Drive and lo-fi
45
49
 
46
50
  `.shape(0..1)` and `.distort(...)` add drive; `.crush(bits)` reduces the bit
@@ -63,6 +67,10 @@ note("c3 eb3 g3 bb3").s("square").crush(5)
63
67
  s("hh*8").pan("0 1 0.25 0.75").gain(0.7)
64
68
  ```
65
69
 
70
+ To set a voice's level and pan on its track's fader instead, the way you
71
+ balance a mix, use `.mix({vol: -6, pan: -0.3})` (vol in dB): see
72
+ [Mixing](mixing.md).
73
+
66
74
  ## Modulation effects
67
75
 
68
76
  `.phaser(rate)` and `.chorus(amount)` add movement; `.compressor(...)`
@@ -45,6 +45,8 @@ s("bd*2 [~ bd] sd ~, hh*8").bank("tr909")
45
45
 
46
46
  `note` plays pitches. Write note names (`c`, `d`, `eb`, `f#`) with an octave
47
47
  number, or MIDI numbers.
48
+ Solfège works too: `note("do re mi")` plays `c d e` (see
49
+ [Melodies with do re mi](melodies-with-do-re-mi.md)).
48
50
 
49
51
  ```js
50
52
  note("c3 eb3 g3 bb3")
@@ -75,6 +75,32 @@ You get `riser.wav`, its loudness and true peak, and an `editUrl` that
75
75
  opens the program here, so you can listen, change it and render it again
76
76
  yourself. Press play above to hear what the agent made.
77
77
 
78
+ ## Example: a bed that mixes itself
79
+
80
+ For music with several voices the agent mixes in the code, as in the
81
+ [Mixing](mixing.md) lesson: one shared reverb on a bus that the voices send
82
+ to, each voice's level with `.mix`, and a limiter on the `master` so the
83
+ loudness target fits under the peak ceiling:
84
+
85
+ ```js
86
+ setcpm(15) // 60 BPM
87
+ bus("verb").fx("Reverb", {dryWetBalance: 1, decayTime: "4s"})
88
+ master.fx("Limiter", {gainDb: 3, ceiling: -3})
89
+ drone: note("c2").s("sawtooth").lpf(300).attack(1).release(1).mix({vol: -6}).send("verb", -18)
90
+ strings: chord("<Cm Ab>").voicing().s("supersaw").attack(2).release(2).lpf(1200).mix({vol: -10}).send("verb", -4)
91
+ pulse: note("c6*8").s("sine").decay(0.1).sustain(0).mix({vol: -8}).send("verb", -14)
92
+ ```
93
+
94
+ ```sh
95
+ tutticode render tension.tc -o tension.wav --loops 3 --tail 3 --fade-out 4 --lufs -18 --json
96
+ ```
97
+
98
+ Each voice's `lufs` in the report (`voices`) says whether the balance is
99
+ what the agent meant; it changes a `.mix` or a `.send` and renders again.
100
+ Drums can share one fader with `group("drums")` and `.group("drums")`
101
+ ([Mixing: groups](mixing-groups.md)), and melodies can be written as
102
+ `note("do re mi")` ([Melodies with do re mi](melodies-with-do-re-mi.md)).
103
+
78
104
  ## Privacy
79
105
 
80
106
  tutticode renders on your computer: the program goes from the agent to the
@@ -64,9 +64,36 @@ Sets chord symbols; also available as .chord(symbols) or .chord().
64
64
  chord("<Bbm9 Fm9>/4").dict("ireal").voicing()
65
65
  ```
66
66
 
67
+ ### `ensembleSet(sections)` *(TuttiDAW only)*
68
+
69
+ TuttiDAW extension, not in Strudel: sets the shared effects of the program's Ensemble Sets (the Sets its .inst("Ensemble:...") voices play in): the Chorus, Delay and Reverb buses and the Set's master, in the devices' own values; once per program, constants only.
70
+
71
+ ```js
72
+ ensembleSet({reverb: {revType: "HALL", revSize: 100}, delay: {dlyTime: "1/8D", dlyFdbk: 40}})
73
+ flute: note("g4 a4 c5 d5").inst("Ensemble:Sample FLUTE", {rev: 60})
74
+ ```
75
+
76
+ ### `env(points, bars?)` *(TuttiDAW only)*
77
+
78
+ TuttiDAW extension, not in Strudel: automation as points "position:value" (positions in bars), joined by straight lines and looped over the last position (or bars); ~k (-1..1) bends the segment ending at a point, _ before a position holds until it, two points at one position jump. A list [[position, value, segment]] is the exact form.
79
+
80
+ ```js
81
+ pad: note("c3").fx("Auto Filter", {frequency: env("0:200 4:4000~.5 8:200")})
82
+ ```
83
+
84
+ ### `form(order)` *(TuttiDAW only)*
85
+
86
+ TuttiDAW extension, not in Strudel: the order the section(...) blocks play in, once per program: section names in order, [..] to group, !n to repeat, name@n for the bars of that one occurrence. The whole form is the program loop (up to 128 bars on TuttiCode).
87
+
88
+ ```js
89
+ section("verse", 8, {drums: s("bd ~ sd ~")})
90
+ section("chorus", 8, {drums: s("bd sd bd sd")})
91
+ form("[verse chorus]!2 verse@4")
92
+ ```
93
+
67
94
  ### `group(name)` *(TuttiDAW only)*
68
95
 
69
- TuttiDAW extension, not in Strudel: declares a group, a group track inside the program; give it .fx(...), .mix(...) and .send(bus, dB), and put voices in it with .group(name).
96
+ TuttiDAW extension, not in Strudel: declares a group, a group track inside the program; give it .fx(...), .mix(...) and .send(bus, dB). On a voice, .group(name) puts its track in that group (a name the program does not declare is a warning).
70
97
 
71
98
  ```js
72
99
  group("drums").fx("Drum Buss").mix({vol: -3})
@@ -123,6 +150,14 @@ One event per cycle with the value.
123
150
  note(pure(60))
124
151
  ```
125
152
 
153
+ ### `ramp(from, to, bars)` *(TuttiDAW only)*
154
+
155
+ TuttiDAW extension, not in Strudel: a straight line from one value to another over a number of bars, then again (a loop); as a mixer level, a send or a device param it is automation with real points. Add .once() to play it once and hold the end.
156
+
157
+ ```js
158
+ kick: s("bd*4").mix({vol: ramp(-60, 0, 4)})
159
+ ```
160
+
126
161
  ### `register(name, fn)`
127
162
 
128
163
  Defines a new method (and a function) from your own code; the pattern comes last.
@@ -156,6 +191,16 @@ Declares external sample references; browser consumers load audio explicitly.
156
191
  samples('github:user/repo')
157
192
  ```
158
193
 
194
+ ### `section(name, bars, tracks?)` *(TuttiDAW only)*
195
+
196
+ TuttiDAW extension, not in Strudel: a section of a song - everything each track plays in it, in one block. Each pattern starts at the section's first bar and loops in it; a track it does not list is silent there. "verse#2" changes only the second verse (the tracks it lists). Sections play in form(...) order, else in the order they are written.
197
+
198
+ ```js
199
+ section("verse", 16, {drums: s("bd ~ sd ~, hh*8"), bass: note("<c2 eb2 g1 bb1>*4")})
200
+ section("chorus", 16, {drums: s("bd sd bd sd, hh*16"), bass: note("<c2 ab1>*8")})
201
+ form("verse chorus verse chorus")
202
+ ```
203
+
159
204
  ### `seq(...patterns)`
160
205
 
161
206
  Squeezes all patterns into one cycle, one after another.
@@ -263,6 +308,15 @@ A value pattern with an inline on/off control (Tutti extension, not in Strudel).
263
308
  note("60 64").mask(toggle(1))
264
309
  ```
265
310
 
311
+ ### `track(name)` *(TuttiDAW only)*
312
+
313
+ TuttiDAW extension, not in Strudel: declares a track of a song with sections and its sound, once - give it .inst(...), .fx(...), .midifx(...), .mix(...), .send(bus, dB) and .group(name). What it plays goes in the section(...) blocks; a track no section lists plays nothing.
314
+
315
+ ```js
316
+ track("bass").inst("Drift:Deep Bass").mix({vol: -3})
317
+ section("verse", 8, {bass: note("<c2 eb2>*4")})
318
+ ```
319
+
266
320
  ## Methods
267
321
 
268
322
  ### `.accelerate(amount?)`
@@ -795,10 +849,10 @@ note("c2 eb2 g2").s("sawtooth").lpf(500).lpenv(4).ftype("<0 1 2>")
795
849
 
796
850
  ### `.fx(name, params?)` *(TuttiDAW only)*
797
851
 
798
- TuttiDAW extension, not in Strudel: adds an audio effect after the instrument of the voice, in call order.
852
+ TuttiDAW extension, not in Strudel: adds an audio effect after the instrument of the voice, in call order; patterns automate params.
799
853
 
800
854
  ```js
801
- note("c4 e4").fx("Reverb:Large Hall", {decayTime: "4s"})
855
+ note("c4 e4").fx("Auto Filter", {frequency: sine.range(300, 3000).slow(4)})
802
856
  ```
803
857
 
804
858
  ### `.gain(level?)`
@@ -883,7 +937,7 @@ note("c d e f").inside(2, rev)
883
937
 
884
938
  ### `.inst(name, params?)` *(TuttiDAW only)*
885
939
 
886
- TuttiDAW extension, not in Strudel: plays the voice on a TuttiDAW instrument ("Device" or "Device:Preset"), params in the device units.
940
+ TuttiDAW extension, not in Strudel: plays the voice on a TuttiDAW instrument ("Device" or "Device:Preset"), params in the device units; patterns automate params.
887
941
 
888
942
  ```js
889
943
  note("c2 eb2").inst("Drift:Deep Bass", {cutoff: 600})
@@ -1059,7 +1113,7 @@ note("[c3,e3,g3]").midifx("Arpeggiator", {style: "UpDown", rate: "1/16"})
1059
1113
 
1060
1114
  ### `.mix(mixer)` *(TuttiDAW only)*
1061
1115
 
1062
- TuttiDAW extension, not in Strudel: the voice's mixer: vol in dB (-70..6), pan -1..1, mute true / false; a bus's, a group's and the master's too.
1116
+ TuttiDAW extension, not in Strudel: the voice's mixer: vol in dB (-70..6), pan -1..1, mute true / false; a bus's, a group's and the master's too. Patterns automate vol and pan.
1063
1117
 
1064
1118
  ```js
1065
1119
  note("c3 e3").mix({vol: -6, pan: -0.3})
@@ -1121,6 +1175,14 @@ Applies f to about 75% of the events.
1121
1175
  s("hh*8").often(x => x.gain(0.4))
1122
1176
  ```
1123
1177
 
1178
+ ### `.once()` *(TuttiDAW only)*
1179
+
1180
+ TuttiDAW extension, not in Strudel: an env() or ramp() plays once from the start, then holds its last value.
1181
+
1182
+ ```js
1183
+ kick: s("bd*4").mix({vol: ramp(-60, 0, 8).once()})
1184
+ ```
1185
+
1124
1186
  ### `.orbit(orbit?)`
1125
1187
 
1126
1188
  Voices on one orbit share its delay and reverb settings.
@@ -102,4 +102,7 @@ stack of note names.
102
102
  note("<[a2,e3,a3,c4] [f2,c3,a3,c4] [c3,g3,c4,e4] [g2,d3,b3,d4]>")
103
103
  ```
104
104
 
105
+ Next: [Melodies with do re mi](melodies-with-do-re-mi.md) writes the same note
106
+ names, and scale roots, in solfège.
107
+
105
108
  Source: https://tuttidaw.com/code/learn/harmony-and-scales