tutticode 0.1.0 → 0.1.1
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.
- package/README.md +7 -3
- package/README.zh-CN.md +6 -3
- package/dist/skills/tutticode/SKILL.md +2 -1
- package/dist/skills/tutticode/references/agent-loop.md +15 -6
- package/dist/skills/tutticode/references/functions.md +1 -1
- package/dist/skills/tutticode/references/melodies-with-do-re-mi.md +59 -0
- package/dist/skills/tutticode-sound-for-media/SKILL.md +15 -8
- package/dist/tutticode.cjs +64 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ anywhere else (for example a project's `.claude/skills`).
|
|
|
52
52
|
tutticode check song.tc # diagnostics, no audio
|
|
53
53
|
tutticode render song.tc -o bed.wav --seconds 12 --fade-out 1.5 --lufs -16 --json
|
|
54
54
|
tutticode render song.tc -o loop.flac --loops 4 --stems stems/
|
|
55
|
-
tutticode open song.tc # open it in TuttiCode for a human
|
|
55
|
+
tutticode open song.tc # open it in TuttiCode for a human (--headless: prints the link)
|
|
56
56
|
cat song.tc | tutticode check - # read the program from stdin
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -60,8 +60,12 @@ cat song.tc | tutticode check - # read the program from stdin
|
|
|
60
60
|
`--format wav|flac|mp3` (default from the `-o` extension),
|
|
61
61
|
`--sample-rate 44100|48000`, `--bit-depth 16|24`, `--stems <dir>`,
|
|
62
62
|
`--fade-in s`, `--fade-out s`, `--tail s`, `--lufs N`, `--true-peak N`,
|
|
63
|
-
`--json`, `--timeout s`.
|
|
64
|
-
|
|
63
|
+
`--limit`, `--json`, `--timeout s`. No file is written clipped: a render
|
|
64
|
+
whose true peak would pass the ceiling (`--true-peak`, default -1 dBTP) is
|
|
65
|
+
lowered by one static gain, and the CLI says so ("Lowered 4.6 dB to avoid
|
|
66
|
+
clipping"). `--lufs` applies one static gain; when the true-peak ceiling
|
|
67
|
+
cannot be met the render fails with `clip`, unless you add `--limit`: a
|
|
68
|
+
true-peak limiter on the mix (not the stems), reported as `limitDb`.
|
|
65
69
|
|
|
66
70
|
`--json` prints the render report: length, bars, tempo, loudness and true
|
|
67
71
|
peak, the gain applied, per-voice levels, the bar and beat grid (seconds, for
|
package/README.zh-CN.md
CHANGED
|
@@ -45,16 +45,19 @@ Claude Code、Copilot CLI 和 Codex 分别从 `~/.claude/skills`、`~/.copilot/s
|
|
|
45
45
|
tutticode check song.tc # 只检查,不出声音
|
|
46
46
|
tutticode render song.tc -o bed.wav --seconds 12 --fade-out 1.5 --lufs -16 --json
|
|
47
47
|
tutticode render song.tc -o loop.flac --loops 4 --stems stems/
|
|
48
|
-
tutticode open song.tc # 在 TuttiCode 里打开给人看
|
|
48
|
+
tutticode open song.tc # 在 TuttiCode 里打开给人看(--headless:只打印链接)
|
|
49
49
|
cat song.tc | tutticode check - # 从 stdin 读程序
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
`render` 参数: `--seconds|--bars|--loops N`、`--bpm N`、
|
|
53
53
|
`--format wav|flac|mp3`(默认按 `-o` 的扩展名)、`--sample-rate 44100|48000`、
|
|
54
54
|
`--bit-depth 16|24`、`--stems <目录>`、`--fade-in 秒`、`--fade-out 秒`、
|
|
55
|
-
`--tail 秒`、`--lufs N`、`--true-peak N`、`--json`、`--timeout 秒`。
|
|
55
|
+
`--tail 秒`、`--lufs N`、`--true-peak N`、`--limit`、`--json`、`--timeout 秒`。
|
|
56
|
+
不会写出削波的文件:true peak 超过上限(`--true-peak`,默认 -1 dBTP)的渲染会
|
|
57
|
+
整体降低一次静态增益,CLI 会说明("Lowered 4.6 dB to avoid clipping")。
|
|
56
58
|
`--lufs` 只做一次静态增益;如果达不到 true peak 上限,会以 `clip` 失败,而不是
|
|
57
|
-
|
|
59
|
+
偷偷加限制器;需要时加 `--limit`:对混音(不含分轨)加 true peak 限制器,
|
|
60
|
+
报告里的 `limitDb` 是它最多压下的量。
|
|
58
61
|
|
|
59
62
|
`--json` 输出渲染报告:时长、小节数、速度、响度和 true peak、施加的增益、每个
|
|
60
63
|
声部的电平、小节和拍的时间点(秒,用来对齐剪辑点)、检查报告、分析结果、
|
|
@@ -125,6 +125,7 @@ Read only what the task needs:
|
|
|
125
125
|
- `references/mini-notation.md`: Mini-notation. The rhythm language inside the quotes - sequences, rests, groups, alternation, polymeter, Euclidean rhythms and chance.
|
|
126
126
|
- `references/time-and-structure.md`: Time and structure. Functions that speed up, shift, reverse, repeat and combine patterns, and arrange them into sections.
|
|
127
127
|
- `references/harmony-and-scales.md`: Harmony and scales. Note names and octaves, scale degrees, transposition, chord symbols, voicings and arpeggios.
|
|
128
|
+
- `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.
|
|
128
129
|
- `references/sounds.md`: Sounds. Drum kits and banks, synth waveforms, General MIDI instrument names, Ensemble sounds and your own samples.
|
|
129
130
|
- `references/effect-controls.md`: Effect controls. Filters, reverb, delay, distortion, panning, envelopes and synth controls - and moving them with signals.
|
|
130
131
|
- `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.
|
|
@@ -136,5 +137,5 @@ Read only what the task needs:
|
|
|
136
137
|
- `references/functions.md`: every function, method and signal, with signature, description and example (generated from the engine).
|
|
137
138
|
- `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).
|
|
138
139
|
|
|
139
|
-
Language: PATTERN LANGUAGE 1.2.0, engine
|
|
140
|
+
Language: PATTERN LANGUAGE 1.2.0, engine 16.
|
|
140
141
|
Human guide: https://tuttidaw.com/code/learn; agents: https://tuttidaw.com/code/learn/for-agents
|
|
@@ -40,8 +40,9 @@ differences are marked *local* or *web*.
|
|
|
40
40
|
|
|
41
41
|
*local* returns `files` (`audio`, `stems`: paths written on disk),
|
|
42
42
|
`editUrl`, `window` (`bars`, `seconds`, `bpm`), `loudness` (`lufs`,
|
|
43
|
-
`truePeak` of the delivered file, `gainDb` applied for a `lufs` target
|
|
44
|
-
|
|
43
|
+
`truePeak` of the delivered file, `gainDb` applied for a `lufs` target or
|
|
44
|
+
to avoid clipping, `limitDb` with `limit`), `warnings` (what was done to
|
|
45
|
+
the audio), `voices[]` (each voice's solo `lufs` and `truePeak`), `barTimes` /
|
|
45
46
|
`beatTimes` (seconds of each bar and beat: sync video cuts to them),
|
|
46
47
|
`check` and `analysis`. *web* returns `analysis`, a `listenUrl` (24 h) and
|
|
47
48
|
signed audio links.
|
|
@@ -71,11 +72,19 @@ or mute (`_`) the voices you are not listening to.
|
|
|
71
72
|
(whole loops ring out past their end). `tail` adds seconds after the
|
|
72
73
|
length for reverbs and delays and makes the file exactly length + tail;
|
|
73
74
|
`fadeIn` / `fadeOut` are linear, in seconds.
|
|
75
|
+
- No file is written clipped: without `lufs`, a render whose true peak
|
|
76
|
+
would pass `truePeak` (default -1 dBTP) is lowered by one static gain
|
|
77
|
+
(negative `gainDb`, a line in `warnings`); a stem that would clip on
|
|
78
|
+
its own is lowered further (its `gainDb` in `voices[]`). *web* lowers
|
|
79
|
+
the audio file the same way and says so in `warnings`; its `analysis`
|
|
80
|
+
measures the program before that gain.
|
|
74
81
|
- `lufs` applies one static gain to that integrated loudness, the same on
|
|
75
|
-
the stems. When the true peak would pass `truePeak`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
the stems. When the true peak would pass `truePeak` the render fails
|
|
83
|
+
with code `clip`: ask for a lower `lufs`, make the program less peaky
|
|
84
|
+
(lower its loudest voice), or add `limit: true` (`--limit`), a
|
|
85
|
+
true-peak limiter on the mix (not the stems) that reports the most it
|
|
86
|
+
took off in `limitDb`. Under 0.4 s there is no integrated loudness and
|
|
87
|
+
`lufs` is ignored.
|
|
79
88
|
- Exit codes (CLI): 0 ok, 1 errors in the code or `clip`, 2 bridge or
|
|
80
89
|
browser problem (run `tutticode doctor`), 3 usage.
|
|
81
90
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Melodies with do re mi
|
|
2
|
+
|
|
3
|
+
Write notes in solfège (do re mi fa sol la si) wherever a note name goes; strudel.cc gets letters.
|
|
4
|
+
|
|
5
|
+
## Solfège is a note name
|
|
6
|
+
|
|
7
|
+
Wherever a note name goes, you can write its solfège name instead:
|
|
8
|
+
`do re mi fa sol la si`. TuttiCode uses fixed do: `do` is always C, `re` is D,
|
|
9
|
+
`sol` is G, whatever the key. `so` and `ti` work too (the same as `sol` and
|
|
10
|
+
`si`).
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
note("do re mi fa sol la si do4")
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Only the name differs: `note("do re mi")` plays exactly what `note("c d e")`
|
|
17
|
+
plays.
|
|
18
|
+
|
|
19
|
+
## Sharps, flats and octaves
|
|
20
|
+
|
|
21
|
+
Add sharps, flats and octaves as you would to a letter: `do#4`, `mib` (E flat),
|
|
22
|
+
`sib3` (B flat), `fa#`. A name without an octave is in octave 3, like `c`.
|
|
23
|
+
Capitals work too: `Do`, `SOL`.
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
note("<do4 mib4 sol4 sib4> [fa#3 la3]").s("piano")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Scales and chords
|
|
30
|
+
|
|
31
|
+
Scale roots take solfège: `.scale("re:minor")`. Stack names with commas to
|
|
32
|
+
play a chord.
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
n("0 2 4 <7 6>").scale("<re:dorian sol:mixolydian>")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
note("<[do3,mi3,sol3] [la2,do3,mi3] [fa2,la2,do3] [sol2,si2,re3]>").s("piano")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Chord symbols (`chord("C^7")`, `.voicings()`) keep their letters: there `do`
|
|
43
|
+
already means D diminished (`D` and `o`).
|
|
44
|
+
|
|
45
|
+
## Where a word is not a note
|
|
46
|
+
|
|
47
|
+
In `s()` and `sound()` words are sound names, so `s("do")` looks for a sound
|
|
48
|
+
called "do". `n()` is also a sample or soundfont index, so `n("do")` is a
|
|
49
|
+
word too: write `note("do")`. Solfège means a pitch only in `note()` /
|
|
50
|
+
`.note()`, `.scale()` roots, `.mode()` and `.anchor()`, and `.add()` /
|
|
51
|
+
`.sub()` / `.transpose()` on notes.
|
|
52
|
+
|
|
53
|
+
## Opening in strudel.cc
|
|
54
|
+
|
|
55
|
+
strudel.cc reads letter names only. "Open in strudel.cc" rewrites solfège to
|
|
56
|
+
letters for you (`"do re mi"` becomes `"c d e"`), so the program plays the same
|
|
57
|
+
there.
|
|
58
|
+
|
|
59
|
+
Source: https://tuttidaw.com/code/learn/melodies-with-do-re-mi
|
|
@@ -52,19 +52,26 @@ Recipe index:
|
|
|
52
52
|
| Broadcast (EBU R128) | -23 |
|
|
53
53
|
| Short sound effects | -18..-26: peaky, see each recipe |
|
|
54
54
|
|
|
55
|
-
- True peak ceiling -1 dBTP (the default
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`
|
|
60
|
-
|
|
55
|
+
- True peak ceiling -1 dBTP (the default, `truePeak`). No file is written
|
|
56
|
+
clipped: without `lufs`, a render that would pass the ceiling is lowered
|
|
57
|
+
by one static gain (a negative `gainDb` and a line in `warnings`). `lufs`
|
|
58
|
+
is one static gain: a peaky program (most drum programs at -14..-16)
|
|
59
|
+
fails with `clip`. Then lower the target, lower the loudest voice's
|
|
60
|
+
`.gain` and render again, end the program with
|
|
61
|
+
`master.fx("Limiter", {gainDb: 3, ceiling: -3})` (the program's own bus
|
|
62
|
+
catches the peaks before the gain, as the music recipes do), or add
|
|
63
|
+
`limit: true` (`--limit`): the render's true-peak limiter holds the
|
|
64
|
+
ceiling and reports the most it took off (`limitDb`). More than about
|
|
65
|
+
6 dB of it flattens the drums: then lower the target instead.
|
|
61
66
|
- Exact durations: `seconds` cuts exactly. A one-shot effect is one bar
|
|
62
67
|
long: `setcpm` sets the bar (`setcpm(60)` = 1 s, `setcpm(15)` = 4 s).
|
|
63
68
|
- Video beds: `fadeIn` 1-3 s, `fadeOut` 2-5 s, `tail` for the reverb.
|
|
64
69
|
- Loops for apps and games: `loops` N with `tail: 0`, no fades, so the file
|
|
65
70
|
is exactly N loops and repeats in time; keep the last step short.
|
|
66
|
-
- Stems: `stems: true` writes one file per voice (named by the labels);
|
|
67
|
-
|
|
71
|
+
- Stems: `stems: true` writes one file per voice (named by the labels); the
|
|
72
|
+
mix's static gain applies to the stems too, so they still sum to the mix.
|
|
73
|
+
A stem that would clip on its own is lowered further (its `gainDb` in
|
|
74
|
+
`voices`, a line in `warnings`); the limiter never touches stems.
|
|
68
75
|
|
|
69
76
|
## Quality bar: never deliver
|
|
70
77
|
|
package/dist/tutticode.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/* tutticode v0.1.
|
|
2
|
+
/* tutticode v0.1.1 | https://tuttidaw.com */
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
@@ -3932,7 +3932,7 @@ function validRender(m) {
|
|
|
3932
3932
|
return "lufs must be -40..-5";
|
|
3933
3933
|
if (!optional(m.truePeak, (v) => finite(v) && v >= -12 && v <= 0))
|
|
3934
3934
|
return "truePeak must be -12..0";
|
|
3935
|
-
for (const key of ["stems", "analyze"])
|
|
3935
|
+
for (const key of ["stems", "analyze", "limit"])
|
|
3936
3936
|
if (!optional(m[key], (v) => typeof v === "boolean"))
|
|
3937
3937
|
return `${key} must be a boolean`;
|
|
3938
3938
|
return void 0;
|
|
@@ -3988,6 +3988,8 @@ function bridgePageUrl(origin, locale, port, token) {
|
|
|
3988
3988
|
}
|
|
3989
3989
|
var MAX_LABEL_CHARS = 64;
|
|
3990
3990
|
var MAX_VERSION_CHARS = 128;
|
|
3991
|
+
var MAX_WARNINGS = 32;
|
|
3992
|
+
var MAX_WARNING_CHARS = 1e3;
|
|
3991
3993
|
var MAX_EDIT_URL_CHARS = 1e6;
|
|
3992
3994
|
var MAX_GRID_ENTRIES = 2e4;
|
|
3993
3995
|
var MAX_RENDER_SECONDS = BRIDGE_MAX_SECONDS + 60;
|
|
@@ -4023,6 +4025,13 @@ function validateRenderMeta(raw, expect) {
|
|
|
4023
4025
|
return no("meta.lufs / meta.truePeak out of range");
|
|
4024
4026
|
if (!inRange(raw.gainDb, -100, 100))
|
|
4025
4027
|
return no("meta.gainDb out of range");
|
|
4028
|
+
if (!optional(raw.limitDb, (v) => inRange(v, 0, 100)))
|
|
4029
|
+
return no("meta.limitDb out of range");
|
|
4030
|
+
if (!optional(
|
|
4031
|
+
raw.warnings,
|
|
4032
|
+
(v) => Array.isArray(v) && v.length <= MAX_WARNINGS && v.every((w) => typeof w === "string" && w.length <= MAX_WARNING_CHARS)
|
|
4033
|
+
))
|
|
4034
|
+
return no("meta.warnings must be at most 32 short strings");
|
|
4026
4035
|
const barTimes = grid(raw.barTimes);
|
|
4027
4036
|
const beatTimes = grid(raw.beatTimes);
|
|
4028
4037
|
if (!barTimes || !beatTimes)
|
|
@@ -4046,7 +4055,14 @@ function validateRenderMeta(raw, expect) {
|
|
|
4046
4055
|
const voicePeak = level(v.truePeak, -200, 40);
|
|
4047
4056
|
if (voiceLufs === void 0 || voicePeak === void 0)
|
|
4048
4057
|
return no("meta.voices levels out of range");
|
|
4049
|
-
|
|
4058
|
+
if (!optional(v.gainDb, (g) => inRange(g, -100, 100)))
|
|
4059
|
+
return no("meta.voices gainDb out of range");
|
|
4060
|
+
voices.push({
|
|
4061
|
+
label: v.label,
|
|
4062
|
+
lufs: voiceLufs,
|
|
4063
|
+
truePeak: voicePeak,
|
|
4064
|
+
...v.gainDb !== void 0 ? { gainDb: v.gainDb } : {}
|
|
4065
|
+
});
|
|
4050
4066
|
}
|
|
4051
4067
|
if (!isObject(raw.check))
|
|
4052
4068
|
return no("meta.check must be a report");
|
|
@@ -4067,6 +4083,8 @@ function validateRenderMeta(raw, expect) {
|
|
|
4067
4083
|
lufs,
|
|
4068
4084
|
truePeak,
|
|
4069
4085
|
gainDb: raw.gainDb,
|
|
4086
|
+
...raw.limitDb !== void 0 ? { limitDb: raw.limitDb } : {},
|
|
4087
|
+
...raw.warnings !== void 0 ? { warnings: [...raw.warnings] } : {},
|
|
4070
4088
|
barTimes,
|
|
4071
4089
|
beatTimes,
|
|
4072
4090
|
stems,
|
|
@@ -4207,12 +4225,16 @@ function formatCheckSummary(report) {
|
|
|
4207
4225
|
)} s)`;
|
|
4208
4226
|
}
|
|
4209
4227
|
function formatRenderSummary(meta, files) {
|
|
4228
|
+
const gain = meta.gainDb ? `, gain ${meta.gainDb > 0 ? "+" : ""}${fixed(meta.gainDb)} dB` : "";
|
|
4229
|
+
const limited = meta.limitDb ? `, limited up to ${fixed(meta.limitDb)} dB` : "";
|
|
4210
4230
|
const lines = [
|
|
4211
4231
|
`Wrote ${files.mix} (${fixed(meta.seconds)} s, ${meta.bars} bars at ${fixed(
|
|
4212
4232
|
meta.bpm,
|
|
4213
4233
|
0
|
|
4214
|
-
)} BPM, ${fixed(meta.lufs)} LUFS, ${fixed(meta.truePeak)} dBTP${
|
|
4234
|
+
)} BPM, ${fixed(meta.lufs)} LUFS, ${fixed(meta.truePeak)} dBTP${gain}${limited})`
|
|
4215
4235
|
];
|
|
4236
|
+
for (const warning of meta.warnings ?? [])
|
|
4237
|
+
lines.push(`Note: ${warning}`);
|
|
4216
4238
|
const stems = Object.values(files.stems);
|
|
4217
4239
|
if (stems.length)
|
|
4218
4240
|
lines.push(`Stems: ${stems.length} files in ${(0, import_node_path.dirname)(stems[0])}`);
|
|
@@ -4239,7 +4261,7 @@ var wrapper_default = import_websocket.default;
|
|
|
4239
4261
|
var import_node_fs2 = require("node:fs");
|
|
4240
4262
|
var import_node_os = require("node:os");
|
|
4241
4263
|
var import_node_path2 = require("node:path");
|
|
4242
|
-
var VERSION = true ? "0.1.
|
|
4264
|
+
var VERSION = true ? "0.1.1" : readPackageVersion();
|
|
4243
4265
|
function envBrowserArgs() {
|
|
4244
4266
|
const args = process.env.TUTTICODE_BROWSER_ARGS?.split(" ").filter(Boolean);
|
|
4245
4267
|
return args?.length ? args : void 0;
|
|
@@ -4653,6 +4675,7 @@ function parseRender(rest) {
|
|
|
4653
4675
|
tail: { type: "string" },
|
|
4654
4676
|
lufs: { type: "string" },
|
|
4655
4677
|
"true-peak": { type: "string" },
|
|
4678
|
+
limit: { type: "boolean" },
|
|
4656
4679
|
"no-analyze": { type: "boolean" },
|
|
4657
4680
|
json: { type: "boolean" }
|
|
4658
4681
|
});
|
|
@@ -4707,6 +4730,7 @@ function parseRender(rest) {
|
|
|
4707
4730
|
...optional2("tail", number(values, "tail", 0, 60)),
|
|
4708
4731
|
...optional2("lufs", number(values, "lufs", -40, -5)),
|
|
4709
4732
|
...optional2("truePeak", number(values, "true-peak", -12, 0)),
|
|
4733
|
+
...values.limit ? { limit: true } : {},
|
|
4710
4734
|
...values["no-analyze"] ? { analyze: false } : {}
|
|
4711
4735
|
};
|
|
4712
4736
|
return {
|
|
@@ -4751,13 +4775,19 @@ program has errors.`,
|
|
|
4751
4775
|
--stems <dir> also write one file per voice into <dir>
|
|
4752
4776
|
--fade-in S --fade-out S --tail S seconds
|
|
4753
4777
|
--lufs N --true-peak N static gain to a loudness; fails (clip)
|
|
4754
|
-
|
|
4778
|
+
when the true peak would pass the
|
|
4779
|
+
ceiling (default -1 dBTP). Without
|
|
4780
|
+
--lufs a render over the ceiling is
|
|
4781
|
+
lowered (one static gain, reported)
|
|
4782
|
+
--limit true-peak limiter instead of failing
|
|
4783
|
+
(clip) or lowering; reported
|
|
4755
4784
|
--no-analyze skip the per-voice analysis
|
|
4756
4785
|
--json print the RenderMeta + written files
|
|
4757
4786
|
--timeout S give up after S seconds (default 600)`,
|
|
4758
4787
|
open: `tutticode open <file|-> [bridge flags]
|
|
4759
4788
|
|
|
4760
|
-
Opens the program in a visible TuttiCode tab (tuttidaw.com/code)
|
|
4789
|
+
Opens the program in a visible TuttiCode tab (tuttidaw.com/code). With
|
|
4790
|
+
--headless it opens nothing and prints the link.`,
|
|
4761
4791
|
mcp: `tutticode mcp [bridge flags]
|
|
4762
4792
|
|
|
4763
4793
|
MCP stdio server: code_check, code_render, code_compare, code_open,
|
|
@@ -7015,13 +7045,17 @@ var renderProperties2 = {
|
|
|
7015
7045
|
type: "number",
|
|
7016
7046
|
minimum: -40,
|
|
7017
7047
|
maximum: -5,
|
|
7018
|
-
description: 'Static gain to this integrated loudness (e.g. -14 for streaming, -16 for podcasts / video beds). Fails with code "clip"
|
|
7048
|
+
description: 'Static gain to this integrated loudness (e.g. -14 for streaming, -16 for podcasts / video beds). Fails with code "clip" when truePeak cannot be met, unless limit is true.'
|
|
7019
7049
|
},
|
|
7020
7050
|
truePeak: {
|
|
7021
7051
|
type: "number",
|
|
7022
7052
|
minimum: -12,
|
|
7023
7053
|
maximum: 0,
|
|
7024
|
-
description: "dBTP ceiling
|
|
7054
|
+
description: "dBTP ceiling (default -1). Without lufs, a render that would pass it is lowered by one static gain (loudness.gainDb, warnings): no file is written clipped."
|
|
7055
|
+
},
|
|
7056
|
+
limit: {
|
|
7057
|
+
type: "boolean",
|
|
7058
|
+
description: 'Opt-in true-peak limiter: peaks over truePeak are limited instead of failing with "clip" (with lufs) or lowering the whole render (without). Use it to reach -14 / -16 LUFS on drum-heavy programs; loudness.limitDb reports the most it took off. Stems are not limited.'
|
|
7025
7059
|
},
|
|
7026
7060
|
out: {
|
|
7027
7061
|
type: "string",
|
|
@@ -7195,7 +7229,8 @@ function createToolHandler(deps) {
|
|
|
7195
7229
|
"fadeOut",
|
|
7196
7230
|
"tail",
|
|
7197
7231
|
"lufs",
|
|
7198
|
-
"truePeak"
|
|
7232
|
+
"truePeak",
|
|
7233
|
+
"limit"
|
|
7199
7234
|
])
|
|
7200
7235
|
if (args[key] !== void 0)
|
|
7201
7236
|
command[key] = args[key];
|
|
@@ -7241,8 +7276,10 @@ function createToolHandler(deps) {
|
|
|
7241
7276
|
loudness: {
|
|
7242
7277
|
lufs: meta.lufs,
|
|
7243
7278
|
truePeak: meta.truePeak,
|
|
7244
|
-
gainDb: meta.gainDb
|
|
7279
|
+
gainDb: meta.gainDb,
|
|
7280
|
+
...meta.limitDb ? { limitDb: meta.limitDb } : {}
|
|
7245
7281
|
},
|
|
7282
|
+
...meta.warnings?.length ? { warnings: meta.warnings } : {},
|
|
7246
7283
|
voices: meta.voices,
|
|
7247
7284
|
barTimes: meta.barTimes,
|
|
7248
7285
|
beatTimes: meta.beatTimes,
|
|
@@ -18466,6 +18503,20 @@ async function render(command, io) {
|
|
|
18466
18503
|
}
|
|
18467
18504
|
async function open(command, io) {
|
|
18468
18505
|
const code = await readProgram(command.file, io);
|
|
18506
|
+
const flags = command.bridge;
|
|
18507
|
+
const url = codeLink(
|
|
18508
|
+
normalizeOrigin(flags.origin ?? "") ?? BRIDGE_ORIGINS[0],
|
|
18509
|
+
flags.locale ?? "en",
|
|
18510
|
+
code
|
|
18511
|
+
);
|
|
18512
|
+
if (flags.show === false) {
|
|
18513
|
+
io.stdout.write(
|
|
18514
|
+
`No browser window was opened (--headless). Open the program in TuttiCode:
|
|
18515
|
+
${url}
|
|
18516
|
+
`
|
|
18517
|
+
);
|
|
18518
|
+
return EXIT_OK;
|
|
18519
|
+
}
|
|
18469
18520
|
const outcome = await runJob(
|
|
18470
18521
|
{ type: "open", code },
|
|
18471
18522
|
command.bridge,
|
|
@@ -18480,7 +18531,8 @@ async function open(command, io) {
|
|
|
18480
18531
|
command.file,
|
|
18481
18532
|
io
|
|
18482
18533
|
);
|
|
18483
|
-
io.stdout.write(
|
|
18534
|
+
io.stdout.write(`Opened in TuttiCode: ${url}
|
|
18535
|
+
`);
|
|
18484
18536
|
return EXIT_OK;
|
|
18485
18537
|
}
|
|
18486
18538
|
async function mcp(flags, io) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tutticode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Turn TuttiCode (tuttidaw.com/code pattern code) into audio files from any agent: a CLI and an MCP server that render in your own browser with the TuttiDAW engine.",
|
|
6
6
|
"license": "MIT",
|