内容
# 螺旋体
[](https://github.com/richer-richard/cochlea/actions/workflows/ci.yml)
[](https://richer-richard.github.io/cochlea/)
**用于智能体的无头音频引擎。** 编写一份记谱数据,离线渲染为确定性的PCM,然后 *聆听数字* —— 音量、起始时间、音高、音调、频谱图 —— 并确认您听到的内容。 编曲 → 渲染 → 探测 → 验证,无需人工耳(也无需音频设备)参与循环。

*智能体看到的内容:`examples/scores/first_light.ron`的梅尔频谱图 —— 在下面的示例中使用的记谱 —— 渲染并探测后。 没有PCM的可见痕迹。*
```rust
use cochlea_score::*;
let score = Score::new(SampleRate(48_000), Ppq(960))
.time_signature(4, 4)
.tempo(Ticks(0), Bpm(120.0))
.track("lead", Instrument::preset("saw_lead"))
.note("lead", bar(1).beat(1), Dur::quarter(), Pitch::A4, Vel(96))
.automate("lead", Param::CUTOFF_HZ,
keys![(bar(1), 400.0, ease_in_out()), (bar(3), 4_000.0)]);
let rendered = cochlea_render::render(&score)?;
rendered.write_wav("mix.wav")?;
use cochlea_verify::{VerifyExt, Tol, Ms, Cents, Db};
let report = rendered.verify(&score)
.true_peak_below(-1.0)
.pitch_matches_score("lead", Cents(10.0))
.monotone("lead", Param::CUTOFF_HZ, bar(1)..bar(3))
.silent_after(bar(5))
.run();
assert!(report.passed);
```
或者完全从命令行开始,记谱以RON格式:
```
cochlea render score.ron --out mix.wav --stems stems/ --verify
cochlea probe input.wav --json report.json --spectro spec.png
cochlea probe input.wav --digest --window-ms 500
cochlea diff a.wav b.wav --tier2
cochlea lint score.ron
cochlea spectro input.wav --out spec.png --sheet --bars-per-tile 8
```
`cochlea probe`适用于 **任何** WAV文件 —— 以及FLAC,解码比特精确,仍然无需ffmpeg —— 无需记谱。 这是前门:指向您未渲染的音频,获得相同的JSON报告和频谱图,智能体使用它来审查自己的工作。
## 智能体如何聆听
编曲 → 渲染 → 探测(JSON)→ 频谱图(一张视觉调用)→ 验证
1. **编曲** 一份记谱数据(RON,或上面的Rust构建器)。
2. **渲染** 为确定性的PCM —— `cochlea render score.ron --out mix.wav`。
3. **探测** 混音成一份紧凑的JSON报告(音量、起始时间、音高、音调、沉默、剪辑)—— `cochlea probe mix.wav --json report.json`。 没有图像,没有音频:智能体读取数字。
4. **查看** 当数字不够时 —— `cochlea spectro mix.wav --out spec.png` 渲染一张小PNG图像,智能体在一次视觉调用中审查,而不是推理原始样本。
5. **验证** —— `cochlea render score.ron --verify` 运行记谱的嵌入式断言,并在失败时退出,非零带有一个机器可读的JSON失败报告。
经济效益是重点,而不是事后考虑。 上面的`first_light`渲染是7秒的48 kHz/32位浮点PCM,重量为2.7 MB;相同设置下,3分钟的作品约为66 MB —— 不是要交给智能体作为文本,更不用说逐样本读取。 其探测报告约为2.5 KB的JSON(v2模式,修剪到有趣的字段 —— `tempo`/`stereo`/`structure`是新的;这个作品很短,接近单声道,所以它们在这里接近地板,见下面的摘要):
```json
{
"schema_version": 2,
"source": { "sample_rate": 48000, "channels": 2, "duration_ms": 7035.708333333333 },
"loudness": { "integrated_lufs": -22.70045487928478, "true_peak_dbtp": -15.910817022082783, "lra": 10.607660373688798 },
"onsets": { "count": 6, "times_ms": [1077.33, 2149.33, 2346.67, 3221.33, 4538.67, 5034.67] },
"pitch": { "voiced_ratio": 0.9847560975609756, "median_f0_hz": 110.00194603797897 },
"key": { "tonic": "E", "mode": "major", "confidence": 0.8093960265638273 },
"tempo": { "bpm": 55.97014925373134, "confidence": 0.003937017247067399, "clear_rhythm": false },
"stereo": { "width": 0.02967719705208343, "correlation": 0.9981362354107913, "balance": -0.0016380539212361243 },
"structure": { "section_count": 1, "confidence": 0.0 },
"silence": { "trailing_ms": 2485.708333333333 },
"clipping": { "clipped_samples": 0, "true_peak_over_0dbtp": false }
}
```
而频谱图是一张小图像。 这里是 `title_cue` 演示 —— 一个垫子的 `cutoff_hz` 自动控制从250 Hz → 5000 Hz 跨越1-3小节:

*帧顶部的暗带随着扫描的进行而变窄,更多的高频能量被允许通过。 智能体直接从图像中读取这一点;演示中的 `Monotone(track: "pad", param: "cutoff_hz", ...)` 断言以数字方式检查相同的内容。*
对于一个完整的作品,无论长度如何,只需一个图像,使用 `--sheet` 将频谱图拼接成一个联系表,而不是一条长条(这里每行2小节, `--bars-per-tile 2`):

## 无需上下文窗口读取音频
`probe --digest` 跳过JSON,直接打印一个确定性的文本摘要 —— 每个特征维度一行,然后是一个窗口化的时间线,限制在 ~40行。 `drum_groove` 演示(20.8秒,4个轨道,波浪2节奏/立体声/结构维度在一屏内)的实际输出:
```
cochlea digest: 20.755s 2ch 48000Hz
loudness: integrated=-19.91 momentary_max=-17.54 true_peak=-4.78 lra=1.73
key: G major (conf 0.28) pitch: voiced=26% median=55.0Hz (A1 +0.5c)
tempo: 110.3bpm (conf 0.01) clear_rhythm=false
stereo: width=0.01 corr=1.00 bal=-0.00
structure: 1 section
onsets: count=32 rate=1.54/s
silence: leading=0ms trailing=2545ms
clipping: clipped=0 over_0dbtp=false
timeline: window=1000ms bucket=1x rows=21
idx t(s) rms peak ons f0 flags
0 0.000-1.000 -25.71 -7.38 2 55.0 -
1 1.000-2.000 -25.60 -7.87 2 55.0 -
...
17 17.000-18.000 -34.66 -16.80 1 54.7 -
18 18.000-19.000 -58.77 -41.61 0 - -
19 19.000-20.000 -118.98 -103.16 0 54.7 S
20 20.000-20.755 -161.14 -144.94 0 54.8 S
```
`cochlea diff` 比较两个文件在特征空间而不是字节对字节 —— “我的更改是否实现了我想要的”,而不是“文件是否比特相同”。 `first_light.wav` 和 `title_cue.wav` 之间的实际输出差异:
```
verdict: different (duration, loudness, onsets, key)
duration a->b +1264.3 ms
loudness integrated -6.22 LU true_peak +6.02 dB lra -8.91 LU
onsets matched=0 mean_offset=- max_offset=- unmatched_a=6 unmatched_b=4
pitch delta +1.0 cents
key a=E major (conf 0.81) b=A minor (conf 0.86) changed=true
segments max_abs_rms_delta 121.71 dB at idx=7
tempo bpm +38.57 bpm clear_rhythm_changed=false
stereo width +0.00 correlation -0.00 balance +0.00
structure section_count +0
```
将渲染与自身进行比较,或相同记谱的重新渲染,判决读取 `byte-identical` —— 上面的确定性合约,从外部检查。 `--tier2` 将判决转换为一个门:退出0表示字节相同或Tier-2等效,退出1否则,因此CI作业或智能体可以在不读取原始样本的情况下捕获回归。
## 智能体作为MCP客户端
`cochlea-mcp` 是stdio MCP服务器,基于CLI使用的相同库 —— 六个工具(`render_score`, `probe_audio`, `spectrogram`, `lint_score`, `probe_digest`, `audio_diff`),每个工具都是匹配的库调用的薄包装,因此任何MCP客户端都可以使用工具调用而不是shell-out的子进程:
```
cargo install --path crates/mcp
claude mcp add cochlea -- cochlea-mcp
```
完整的工具模式、参数和JSON-RPC框架在 [`docs/mcp.md`](docs/mcp.md) 中。
## 安装
尚未在crates.io上发布 —— 从源代码构建 `cochlea` 二进制文件:
```
git clone https://github.com/richer-richard/cochlea
cd cochlea
cargo install --path crates/cli
```
## 概念
- **记谱IR** (`cochlea-score`):轨道,音符,每个参数自动化,阶梯式变速 —— 所有数据,可序列化为RON(`version: 1`,双向测试)。 位置为 `bar(3).beat(2)`,持续时间为精确分数(`Dur::quarter()`,`"3/16"`,带点/三连符糖果);任何网格外的偏移量都是 *错误*,永远不会四舍五入。
- **整数时间是真理**。 960 PPQ的ticks。 BPM一次转换为整数纳秒每四分之一;tick→sample是精确的有理数 u64/u128 算法(通过 `fenestra-anim` 的 `mul_div`)仅在事件调度时间应用一次。 没有累积的浮点秒,没有时钟,属性测试的漂移在10⁹ ticks范围内无漂移。
- **合成器`(`cochlea-synth`)**:基于 [fundsp] 的六个预设 - `sine`,`saw_lead`,`square_bass`,`chord_pad`,`noise_hat`,`pluck`,以及一个 `reverb` 插入。 仪器声明类型化的可自动化参数(名称,单位,范围,默认值);记谱根据该注册表进行验证。 所有的噪音都是基于计数器的RNG,关键 `(seed, sample_index)` -- 随机访问,无状态生成器。
- **渲染器`(`cochlea-render`)**:64样本块在事件边界分割(音符定时是样本准确的;自动化是控制率,~1.3毫秒,48 kHz)。 轨道独立渲染 -- 这是并行单元 *和* 免费干声。 声轨以f64固定轨道顺序求和; 语音分配和最老的音符偷窃是调度数据的纯函数。 主总线在固定轨道顺序下以f64求和;混合结果与干音相同,由定义和测试决定。
- **特性`(`cochlea-features`)**:一个模式化的JSON报告 - 集成LUFS/瞬时最大值/真实峰值/ LRA(通过 [ebur428]) ,基于谱的起始时间,YIN音高和 cents偏差,色度 + Krumhansl-Schmuckler音调,带有校准的`clear_rhythm`标志的拍号跟踪,立体声宽度/相关性/平衡,Foote新颖性结构边界,静音/尾部,剪辑 - 加上一个窗口化的分段时间线,一个LLM大小的文本摘要,以及两个文件之间的特征空间差异。
- **频谱图`(`cochlea-spectro`)**:梅尔频谱图PNG(HTK滤波器库,viridis,时间轴,刻度标记)以及拼贴的联系表,以便智能体通过一次视觉调用查看整个作品。
- **验证`(`cochlea-verify`)**:上述的断言DSL,还可以嵌入记谱的RON格式下 - `cochlea render score.ron --verify` 运行断言,并以非零退出,带有机器可读的JSON失败报告。
## 确定性,有理有据
音频是一个折叠,而不是一个映射:过滤器和延迟带有状态,因此样本纯度不是一个合约。合约分为三个等级:
| 等级 | 声称 | 在哪里 |
|------|-------|-------|
| 1 | **字节相同的PCM** 适用于相同的输入 | 固定的CI目标(x86_64-linux,固定的工具链);每个平台上测试的相同机器可重复性 |
| 2 | **特性容差** 跨平台 | 集成LUFS ±0.1 LU,起始时间 ±2 ms,音高 ±5 cents |
| 3 | **频谱哨兵** | 图片差异,具有每个像素的容差 |
购买一级:`libm` 库专门用于DSP路径中的超越数(标准float方法被 *排除在clippy配置之外,而不是惯例*,没有快速数学,没有隐含FMA(`mul_add`也被排除),拒绝规范化(flush也是实时黑客,不能均匀地跨架构应用 - 参见 `docs/determinism.md`),固定求和顺序,f64主总线,语音样本到样本(fundsp的SIMD块路径 *可证明* 与标量路径不同,被排除),分析FFTs在 `FftPlannerScalar` (无运行时CPU调度)。完整的审计轨迹 - 每个fundsp节点家族,ebur128内部,rustfft调度 - 位于 [`docs/determinism.md`](docs/determinism.md).
## 特性准确性(综合真值,48 kHz)
| 特性 | 固定装置 | 测量 |
|---------|---------|----------|
| 音高(YIN) | 440 Hz正弦 | 440.017 Hz — A4的0.07 cents偏移 |
| 起始时间 | 点击跟踪,0.5秒网格 | ≤ 的4毫秒偏移(帧中心惯例,256样本跳跃) |
| 音调 | C大调三和弦 | C大调,置信度0.79 |
| 音调 | I-IV-V-I垫进程(演示) | C大调 |
| 响度 | -18 dBFS峰值997 Hz正弦 | -21.0 LUFS(≈ -3 LU正弦峰值因子 - 物理学,而不是错误) |
| 静音/尾部 | 1秒音调 + 1秒静音 | 960毫秒拖尾,最后一听觉在1 RMS窗口内 |
| 剪辑 | 驱动方波,限幅 | 计数;0 dBTP以上峰值标志 |
| 拍号 | 120/90 BPM点击跟踪 | ±1 BPM,`clear_rhythm=true`,置信度0.11-0.15(阈值是0.05) |
| 拍号 | `drum_groove`演示(110 BPM节奏) | 110.29 BPM(Δ 0.01),`clear_rhythm=false` - 三个分层周期稀释置信度至0.01,即使BPM本身准确至几乎相等 |
| 结构 | 两个8秒片段,不同音色 | 界限在真实8.0秒界限的1.5秒内 |
| 结构 | 三个8秒片段(A/B/A) | 界限在真实8.0秒和16.0秒界限的1.5秒内 |
## 无ffmpeg设计
无需解码WAV和FLAC(`hound` 和 `symphonia`,两者都是纯Rust实现——FLAC无损,因此解码PCM按规范比特精确,与WAV双胞胎树内测试) ,编写纯WAV,CPU上渲染PNG(`rustfft` + 手工制作的梅尔滤波器库 + viridis LUT + `image`)。 无子进程调用,无系统编解码器,无GPU,无音频设备 - 整个管道是纯Rust依赖图,CI禁止GUI/GPU/设备库进入 `Cargo.lock`(`deny.toml`)。 损失格式(mp3/ogg)是下一个压缩格式目标;仍然无需ffmpeg。
## 断言食谱
```rust
use cochlea_verify::{VerifyExt, Tol, Ms, Cents, Db};
rendered.verify(&score)
// 混音水平和安全裕度:
.integrated_lufs(-14.0, Tol(0.5)) // 流媒体响度目标
.true_peak_below(-1.0) // 插值安全裕度
// 时机:击打是否落在谱写的位置?
.onset_at("drums", bar(17).beat(1), Ms(5.0))
// 音准:每个音符是否如谱写般读取?(单音轨)
.pitch_matches_score("lead", Cents(10.0))
// 滤波器是否实际扫描?(作者曲线,块率)
.monotone("lead", Param::CUTOFF_HZ, bar(1)..bar(3))
// 点击检测远离音符边界:
.no_discontinuity("lead", Db(40.00))
// 作品是否实际结束?
.silent_after(bar(64))
.run();
```
同样的断言嵌入记谱RON:
```ron
verify: [
IntegratedLufs(target: -14.0, tol: 0.5),
TruePeakBelow(dbtp: -1.0),
OnsetAt(track: "drums", at: (17, 1), tol_ms: 5.0),
PitchMatchesScore(track: "lead", tol_cents: 10.0),
Monotone(track: "pad", param: "cutoff_hz", from: (1, 1), to: (3, 1), direction: Rising),
NoDiscontinuity(track: "lead", db: 40.0),
SilentAfter(at: (64, 1)),
]
```
`cochlea render score.ron --verify` 运行它们;失败以JSON返回(`{"passed": false, "checks": [...]}`)和非零退出。
四个演示文案位于 [`demos/`](demos/):`metronome`(样本精确调度,起始时间公差),`chord_pad`(和声读取正确),`title_cue`(一个四小节的电影化开场,断言LUFS目标,单调滤波器扫描,无点击和静音后淡出),以及`drum_groove`(110 BPM,八小节鼓点,断言检测到的拍号,立体声宽度,响度范围和章节计数 - 以及诚实的`HasClearRhythm(false)`,因为分层真实乐器节奏将拍号检测器的置信度稀释到其点击跟踪校准的阈值以下,即使拍号本身几乎准确地落在目标上)。
## 工作空间
```
crates/
score # IR:ticks,tempo map,bar/beat math,notes,automation,RON格式
synth # Patch特征覆盖fundsp,六个预设,参数注册表,计数器RNG
render # 块引擎,语音,干声,f64主总线,WAV输出
features # LUFS/真实峰值,起始时间,音高,色度/音调,拍号,立体声,结构,LRA
decode # WAV + FLAC -> Audio (hound + symphonia, 都是纯Rust)
spectro # 梅尔频谱图 -> PNG,联系表,图像差异
verify # 断言DSL + RON可嵌入规格 + JSON报告
cli # `cochlea` 二进制文件
mcp # MCP stdio服务器(代理调用render/probe/verify作为工具)
```
`features` 和 `spectro` 不依赖于 `score` 或 `synth` -- 在CI中强制执行 -- 这就是为什么 `probe` 可以处理任意WAV文件(以及通过 `decode`,FLAC)。
## 许可证
MIT OR Apache-2.0,随您选择。
[fundsp]: https://crates.io/crates/fundsp
[ebur128]: https://crates.io/crates/ebur128
连接信息
猜你喜欢
暂无推荐服务器