用途:说明 Phase 6 各子阶段的功能设计、文件格式、行为边界与验收标准。 当前状态:已完成。Phase 6-1/6-2/6-5/6-6/6-7 已实现并通过验证;Phase 6-3(最近文件列表 + 拖拽打开)已在架构优化阶段(P0-C)完成;Phase 6-4(基础 MIDI 编辑)永久搁置。 更新时机:Phase 6 各子阶段设计变化、实现状态变化、验收结果更新时。
Phase 6-6 已完成,包含以下内容:
新增文件(5 个):
| 文件 | 职责 |
|---|---|
source/Diagnostics/Log.h |
日志宏定义(DP_LOG_INFO、DP_LOG_WARN、DP_LOG_ERROR、DP_DEBUG_LOG、DP_TRACE_MIDI),纯宏头文件,无 .cpp |
source/Diagnostics/DevPianoLogger.h |
自定义 juce::Logger 子类,路由到 outputDebugString |
source/Diagnostics/DevPianoLogger.cpp |
DevPianoLogger 实现 |
source/Diagnostics/MidiTrace.h |
MIDI 消息描述函数声明(describeMidiMessage) |
source/Diagnostics/MidiTrace.cpp |
MIDI 消息解析实现(note on/off、CC、pitch bend、program change、channel pressure、aftertouch、sysEx、meta、fallback) |
| 接口说明: |
| 接口 | 级别 | Debug 行为 | Release 行为 |
|---|---|---|---|
DP_LOG_INFO(message) |
运行日志 | DBG + Logger | Logger |
DP_LOG_WARN(message) |
运行日志 | DBG + Logger | Logger |
DP_LOG_ERROR(message) |
运行日志 | DBG + Logger | Logger |
DP_DEBUG_LOG(message) |
Debug-only | DBG + Logger | 无输出 |
DP_TRACE_MIDI(message, stage) |
Debug-only | DBG 带 stage 标签 | 无输出 |
describeMidiMessage(const MidiMessage&) |
工具函数 | 格式化字符串 | 同左 |
已接入的关键路径:
| 文件 | 接入点 | 宏 |
|---|---|---|
MidiFileImporter.cpp |
文件读取 + non-note 事件 | DP_TRACE_MIDI, DP_LOG_INFO/WARN/ERROR, DP_DEBUG_LOG |
RecordingEngine.cpp |
录制/回放状态 + 丢弃告警 | DP_DEBUG_LOG, DP_LOG_INFO |
RecordingSessionController.cpp |
Save/Open/Import/Export/Record/Playback 全流程 | DP_LOG_INFO/WARN/ERROR |
PluginHost.cpp |
scan / load / prepare / unload | DP_LOG_INFO/WARN/ERROR |
PresetFlowSupport.cpp |
Preset 保存/导入结果 | DP_LOG_INFO/ERROR |
PluginFlowSupport.cpp |
无效扫描目录 | DP_LOG_WARN |
MainComponent.cpp |
音频设备诊断 | DP_LOG_INFO |
Debug / Release 边界:
DP_DEBUG_LOG和DP_TRACE_MIDI在JUCE_DEBUG或DEBUG条件下启用,Release 下为空宏或空实现(零副作用)。DP_LOG_INFO/WARN/ERROR在 Debug 下同时写DBG(调试器输出窗口)和juce::Logger;Release 下只写Logger。traceMidi()在 Release 下调用juce::ignoreUnused保证无副作用。
字符串编码边界:
- Diagnostics API 以
juce::String为主接口,const char*overload 仅作为兼容层并按 UTF-8 解释。 - 业务代码应直接传
juce::String或字符串字面量给DP_LOG_*/DP_TRACE_MIDI。 - 业务代码不应再通过
.toRawUTF8()把juce::String降级为裸const char*后交给 Diagnostics,避免 JUCE Debug 下把 UTF-8 误判为 ASCII 触发juce::String(const char*)断言。
散落 debug 清理:
已将以下文件中的 juce::Logger::writeToLog 全部替换为 DP_LOG_* 系列宏:
MidiFileImporter.cpp(17 处)RecordingSessionController.cpp(25 处)PresetFlowSupport.cpp(1 处)PluginFlowSupport.cpp(1 处)MainComponent.cpp(1 处)
Phase 6-7 已建立固定 MIDI fixture 样本库与 performance fixture 样本,位于 ../../tests/fixtures/。
详细清单与用途说明见 fixture-inventory.md。
Phase 6-7 的目标是用固定 fixture 样本库取代临时文件和口头复现,为 Phase 6-1(演奏文件保存/打开)、Phase 6-2(播放速度控制)和 Phase 6-5(MIDI 导入增强)提供统一的测试输入基准。
与 Phase 6-6(Diagnostics)和 Phase 6-5(MIDI 导入增强)的关系见 fixture-inventory.md。
Phase 3 实现了录制/回放/MIDI 导出/WAV 导出的 MVP 闭环,但存在一个核心缺口:
录制完即丢失。
用户录制一段演奏后,RecordingTake 仅存在于内存中。关掉程序,数据消失。唯一间接保存方式是导出为 .mid 文件,但那是有损转换(sample-accurate 时间戳 → MIDI tick → 再转回 sample),且丢失 devpiano 内部元数据。
实现核心功能——录制后能保存、保存后能打开、打开后能调速播放。
Phase 6 包含六个子阶段:
| 子阶段 | 目标 | 用户价值 | 备注 |
|---|---|---|---|
| Phase 6-6 | Diagnostics 最小层 | 高(工程基础设施) | ✅ 已完成。已接入 8 个业务文件,散落 Logger 已全部替换。 |
| Phase 6-7 | MIDI/Performance 测试夹具 | 高(工程基础设施) | ✅ 已完成(清单见 fixture-inventory.md)。 |
| Phase 6-1 | 演奏文件保存/打开(.devpiano) |
最高——当前录制完即丢失 | ✅ 已完成。 |
| Phase 6-2 | 播放速度控制(0.5x–2.0x) | 高——练琴刚需 | ✅ 已完成(实时倍率、速度切换重校准、线程安全、边界按钮状态、1.0x 默认不持久化)。 |
| Phase 6-3 | 最近文件列表 + 拖拽打开 | 中——体验增强 | ✅ 已完成(架构优化阶段 P0-C + Phase 7 拖放支持) |
| Phase 6-5 | MIDI 导入增强(sustain/pitch bend/program change) | 中——提升回放保真度 | ✅ 已完成。 |
| 能力 | 状态 | 依据 |
|---|---|---|
| 录制 | 已实现 | RecordingEngine + RecordingFlowSupport |
| 回放 | 已实现 | RecordingEngine::startPlayback() / renderPlaybackBlock() |
| MIDI 导出 | 已实现 | MidiFileExporter::exportTakeAsMidiFile(),标准 MIDI Type 1,960 PPQ |
| WAV 导出 | 已实现 | WavFileExporter,fallback synth 离线渲染 |
| MIDI 导入 | 已实现 | MidiFileImporter,自动选轨,回放 |
| 最近导入/导出路径 | 已实现 | SettingsModel::lastMidiImportPath / lastMidiExportPath |
| 能力 | 状态 | 影响 |
|---|---|---|
| 演奏文件保存 | ✅ 已实现 | 录制数据可持久化为 .devpiano |
| 演奏文件打开 | ✅ 已实现 | 可恢复之前的录制 |
| 播放速度控制 | ✅ 已实现 | 实时倍率 0.5x–2.0x,播放中变速立即生效 |
| 最近文件列表 | ✅ 已实现 | juce::RecentlyOpenedFilesList,下拉菜单快速打开 |
| 拖拽打开 | ✅ 已实现 | FileDragAndDropTarget,支持 .devpiano / .mid 拖放 |
| 基础编辑 | 未实现 | 录制后无法修改(永久搁置) |
| 非 note 事件导入 | ✅ 已实现 | CC64 sustain、pitch bend、program change 已导入并回放 |
.devpiano 是 devpiano 的私有原生格式,用于无损保存和恢复演奏数据。
与 .mid 的定位对比:
.devpiano |
.mid |
|
|---|---|---|
| 定位 | 内部保存/恢复 | 外部交换 |
| 精度 | 无损(sample-accurate) | 有损(tick-based 往返转换) |
| 受众 | devpiano 自身 | 任何 DAW/播放器 |
| 元数据 | 保留 devpiano 内部语义 | 仅标准 MIDI 语义 |
| 可扩展性 | 不受 MIDI 规范限制 | 受 MIDI 规范约束 |
使用 JSON 文本格式,理由:
- 可读性好,便于调试
- JUCE 内置 JSON 支持(
juce::JSON、juce::DynamicObject) - 无需额外依赖(如 protobuf、flatbuffers)
- 版本演进友好(新增字段向后兼容)
- 文件体积可接受(演奏事件量级通常在万级以下)
{
"version": 2,
"format": "devpiano-performance",
"sampleRate": 44100.0,
"lengthSamples": 2646000,
"metadata": {
"createdAt": "2026-05-03T12:00:00Z",
"title": "",
"notes": ""
},
"events": [
{
"timestampSamples": 44100,
"source": "computerKeyboard",
"midiData": [144, 60, 127]
},
{
"timestampSamples": 88200,
"source": "computerKeyboard",
"midiData": [128, 60, 0]
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
version |
int | 格式版本号,当前为 2。未来格式变更时递增。 |
format |
string | 固定值 "devpiano-performance",用于文件类型识别。 |
sampleRate |
double | 录制时的音频采样率(Hz)。回放时若设备采样率不同,需按比例换算。 |
lengthSamples |
int64 | 录制总长度(samples)。 |
metadata |
object | 元数据,见下表。 |
events |
array | 演奏事件数组,按 timestampSamples 升序排列。 |
| 字段 | 类型 | 说明 |
|---|---|---|
createdAt |
string | ISO 8601 创建时间。 |
title |
string | 用户可选标题,默认为空。 |
notes |
string | 用户可选备注,默认为空。 |
| 字段 | 类型 | 说明 |
|---|---|---|
timestampSamples |
int64 | 事件发生的绝对 sample 位置(相对于录制开始)。 |
source |
string | 事件来源,映射 RecordingEventSource 枚举。 |
midiData |
array[int] | MIDI 消息原始字节(juce::MidiMessage::getRawData() + getRawDataSize())。 |
RecordingEventSource |
JSON 字符串 |
|---|---|
computerKeyboard |
"computerKeyboard" |
realtimeMidiBuffer |
"realtimeMidiBuffer" |
playback |
"playback" |
RecordingTake .devpiano JSON
───────────── ──────────────
sampleRate → sampleRate
lengthSamples → lengthSamples
events[].timestampSamples → events[].timestampSamples
events[].source → events[].source (enum → string)
events[].message → events[].midiData (raw bytes)
序列化方向:RecordingTake → JSON → 文件
反序列化方向:文件 → JSON → RecordingTake
version: 2为当前版本(初始为1,v2 增加了 Base64 序列化优化)。- 未来新增字段(如 tempo map、多轨)通过新增可选字段实现,不改变 version。
- 若事件结构发生不兼容变更(如
midiData编码方式变化),version 递增。 - 读取时检查
version,不支持的版本提示用户升级程序。
实现 RecordingTake 的持久化保存和恢复,形成"录制 → 保存 → 打开 → 回放"闭环。
- 用户点击 Save 按钮 → FileChooser 打开,默认文件名基于当前时间戳(如
performance-20260503-120000.devpiano)。 - 默认目录为上次保存路径(
SettingsModel::lastPerformanceSavePath)。 - 将当前
RecordingTake序列化为 JSON 并写入.devpiano文件。 - 保存完成后 Logger 输出文件路径和事件数。
- 无录制数据时 Save 按钮 disabled。
- 用户点击 Open 按钮 → FileChooser 打开,文件过滤器为
*.devpiano。 - 默认目录为上次打开路径(
SettingsModel::lastPerformanceOpenPath)。 - 读取文件 → 解析 JSON → 构建
RecordingTake→ 设置为当前 take。 - 打开成功后自动开始回放(与 MIDI 导入行为一致)。
- 打开失败(格式错误、版本不支持、文件损坏)时 Logger 输出错误信息,不崩溃。
| 按钮 | 条件 | 状态 |
|---|---|---|
| Save | 有录制 take 且非 playing/recording 状态 | enabled |
| Save | 无 take 或 playing/recording 中 | disabled |
| Open | 非 playing/recording 状态 | enabled |
| Open | playing/recording 中 | disabled |
SettingsModel新增lastPerformanceSavePath和lastPerformanceOpenPath字段。SettingsStore负责读写。- 与现有
lastMidiImportPath/lastMidiExportPath模式一致。
- 不做自动保存。
- 不做文件加密或压缩。
- 不做
.fpm旧格式兼容。 - 不在
.devpiano中保存插件状态、布局或音频设备配置。
- 有录制 take 时,Save 按钮 enabled;点击后 FileChooser 打开。
- 保存的
.devpiano文件可被任何文本编辑器打开,内容为合法 JSON。 - 保存的 JSON 包含
version、format、sampleRate、lengthSamples、events字段。 - 打开之前保存的
.devpiano文件后,回放内容与原始录制一致。 - 打开后 Logger 输出文件路径和事件数。
- 打开损坏或格式错误的
.devpiano文件时,Logger 输出错误信息,不崩溃。 - 无录制数据时 Save 按钮 disabled。
- Recording / Playing 期间 Save 和 Open 按钮均 disabled。
- 保存后再次打开,路径记忆生效(FileChooser 默认定位到上次目录)。
支持 0.5x–2.0x 速度调节,满足练琴场景的慢速回放需求。
- 速度范围:0.5x、0.75x、1.0x(默认)、1.25x、1.5x、2.0x。
- ControlsPanel 增加速度显示 + 增减按钮(
-/+)。 - 速度变更实时生效(播放中调整立即改变回放速率)。
- 每次启动默认 1.0x,不持久化速度值。
RecordingEngine.playbackSpeedMultiplier:std::atomic<double>,默认值1.0,范围0.5–2.0。RecordingEngine.scaledPlaybackLengthSamples/playbackPositionSamples:均为std::atomic<std::int64_t>,跨 audio 线程安全。RecordingEngine.setPlaybackSpeedMultiplier(double):播放中切换时,先重校准playbackPositionSamples(pos × oldSpeed / newSpeed),再更新scaledPlaybackLengthSamples。RecordingEngine.getPlaybackSpeedMultiplier():返回.load()值。renderPlaybackBlock()中用combinedRatio = playbackSampleRateRatio / playbackSpeedMultiplier缩放时间戳,不修改原始事件;block 边界使用[start, end)半开区间,>=上界是刻意的防御性守卫。- 速度变更通过
RecordingSessionController.handlePlaybackSpeedChange()同步到 engine + UI,不写 settings。 MainComponent初始化时硬编码 1.0,不从持久化存储恢复。- ControlsPanel 速度按钮在
recording状态下自动禁用(setEnabled(false)),playing状态下保持可操作;速度到达边界(0.5x / 2.0x)时,setPlaybackSpeed()同步更新setEnabled(),使对应按钮视觉上禁用。
- 不做连续变速滑块(只做离散档位)。
- 不做 pitch correction(变速同时变调是预期行为)。
- 不做 loop / A-B 循环。
- 不做速度值持久化(每次启动均为 1.0x)。
- ControlsPanel 显示当前播放速度,默认
1.00x。 - 点击
-/+按钮可调整速度,范围 0.50x–2.00x。 - 播放中调整速度,回放速率立即变化。
- 每次启动默认 1.0x,不受上次退出时速度影响。
- 0.5x 慢速播放时音符间隔明显拉长,无卡顿。
- 2.0x 快速播放时音符间隔明显缩短,无爆音。
提供最近打开的演奏文件/MIDI 文件列表(最多 10 条),并支持拖拽文件到窗口触发打开。
- 记录最近打开的
.devpiano和.mid文件路径,最多 10 条。 - 列表持久化到
SettingsModel/SettingsStore。 - UI 入口:菜单栏 File 菜单下的 Recent Files 子菜单,或 ControlsPanel 的下拉列表。
- 点击列表项直接打开对应文件。
- 列表中的文件不存在时,点击后提示并从列表移除。
- 支持拖拽
.devpiano和.mid文件到主窗口。 - 拖拽
.devpiano→ 打开演奏文件(Phase 6-1 逻辑)。 - 拖拽
.mid→ 导入 MIDI 文件(Phase 4 逻辑)。 - 拖拽其他文件类型 → 忽略或提示不支持。
- 不做播放列表管理。
- 不做文件分类/标签。
- 不做拖拽到非窗口区域。
- 打开
.devpiano或.mid文件后,最近文件列表更新。 - 最近文件列表最多显示 10 条。
- 点击最近文件列表项可打开对应文件。
- 列表中文件不存在时,点击后提示并移除该项。
- 拖拽
.devpiano文件到窗口可打开。 - 拖拽
.mid文件到窗口可导入。 - 拖拽不支持的文件类型时无反应或提示。
提供最小编辑能力:选中音符 → 删除。
- 将
RecordingTake.events从const vector改为可变结构(或提供删除接口)。 - UI:虚拟键盘面板支持点击选中音符(高亮显示),或提供简单的事件列表视图。
- 选中后按 Delete 键或点击 Delete 按钮删除对应事件。
- 删除操作不可撤销(MVP 阶段不做 undo/redo)。
- 不做添加/移动/量化音符。
- 不做钢琴卷帘编辑器(Phase 8)。
- 不做 undo/redo。
- 不做多选/批量删除。
- 打开或录制的演奏数据中,可选中单个音符。
- 选中音符后可删除,删除后回放不再包含该音符。
- 删除操作不破坏其他事件的时间线。
- 删除后可正常保存为
.devpiano文件。
导入外部 MIDI 文件时,除 note on/off 外,还导入 sustain CC64、pitch bend、program change 等安全的 channel voice 消息,提升回放保真度。
- 扩展
MidiFileImporter,在收集 note on/off 的同时收集:- Control Change(CC64 sustain pedal、CC1 mod wheel 等常用 CC)
- Pitch Bend
- Program Change
- 这些事件作为
PerformanceEvent存入RecordingTake.events,与 note 事件共享同一时间线。 - 回放时这些事件通过
AudioEngine的 MIDI 链路送入插件/fallback synth。
MidiFileImporter.cpp修改:在遍历 track 事件时,对非 note 事件先判断是否为 CC/pitch bend/program change,是则收集为PerformanceEvent,否则仅 trace 诊断后跳过。- 新增计数器:
ccCount、pitchBendCount、programChangeCount。 - 收集到的 CC/pitch bend/program change 事件与 note 事件共用同一时间线转换逻辑(
timestampSeconds → timestampSamples),并更新lastTimestampSamples以正确计算RecordingTake.lengthSamples。 - Logger 输出更新:导入成功后输出 note-on/off 与 CC/pitch-bend/program-change 的分类计数。
- 不导入 SysEx 消息。
- 不导入 meta 事件(tempo、time signature 等)。
- 不导入 RPN/NRPN。
- 不做 GM 音色映射。
- 导入包含 sustain CC64 的 MIDI 文件后,回放时延音踏板效果可听。
- 导入包含 pitch bend 的 MIDI 文件后,回放时弯音效果可听。
- 导入包含 program change 的 MIDI 文件后,回放时音色变化可听(依赖插件支持)。
- 导入不含这些事件的 MIDI 文件时,行为与 Phase 4 一致,无回退。
- Logger 输出导入的非 note 事件数量。
| 风险 | 等级 | 应对 |
|---|---|---|
| JSON 序列化大文件性能 | 低 | 演奏事件量级通常在万级以下,JSON 解析开销可接受 |
| 文件格式版本兼容 | 低 | 初始版本号为 1,新增可选字段不升版本 |
juce::MidiMessage 序列化边界 |
中 | 需确认 getRawData() 对所有消息类型(sysex 等)的行为 |
| 风险 | 等级 | 状态 | 应对 |
|---|---|---|---|
| 极端速度下的音频质量 | 低 | 已完成 | 0.5x/2.0x 范围有限,fallback synth 和插件通常可处理 |
| 速度变更时的 glitch | 中 | ✅ 已解决 | 通过 playbackPositionSamples 重校准(pos × oldSpeed / newSpeed)消除,已验证 note-on/note-off 不错位 |
| 风险 | 等级 | 应对 |
|---|---|---|
| 最近文件列表持久化体积 | 低 | 10 条路径,体积极小 |
| 拖拽与现有 UI 交互冲突 | 中 | 需处理焦点和 drop 位置边界 |
| 风险 | 等级 | 应对 |
|---|---|---|
| 数据模型变更影响回放/导出 | 中 | 需确保删除操作不破坏事件排序和时间线 |
| UI 选中交互复杂度 | 中 | MVP 阶段只做简单选中+删除,不做复杂编辑 |
| 风险 | 等级 | 应对 |
|---|---|---|
| 非 note 事件增加文件体积 | 低 | CC/pitch bend 事件量远少于 note 事件 |
| fallback synth 不响应 CC/pitch bend | 低 | 依赖插件支持;fallback synth 可忽略不支持的事件 |
- Save 按钮:有 take 且非录制/播放中时 enabled。
- 保存的
.devpiano文件为合法 JSON,包含完整事件数据。 - Open 按钮:非录制/播放中时 enabled。
- 打开
.devpiano文件后回放内容与原始录制一致。 - 打开损坏文件时不崩溃,Logger 输出错误。
- 路径记忆:Save/Open 均记住上次目录。
- 速度显示 + 增减按钮可用,范围 0.50x–2.00x。
- 播放中变速立即生效。
- 速度值持久化恢复。
- 最近文件列表最多 10 条,点击可打开。
- 拖拽
.devpiano/.mid文件到窗口可打开。
- 可选中并删除单个音符。
- 删除后回放和保存均正确。
- 导入 sustain CC64、pitch bend、program change 并回放。
- 不含这些事件的 MIDI 文件行为无回退。
所有 MIDI/Performance 测试夹具的完整清单、目录结构、预期用途及验收标准见 fixture-inventory.md。
Fixtures 实际存放于 ../../tests/fixtures/。
用途:记录 Phase 6 演奏文件保存/打开、播放速度控制、最近文件列表、基础编辑和 MIDI 导入增强的专项验收测试。 当前状态:Phase 6-1 已完成,Phase 6-2 已完成,Phase 6-7 已完成。 更新时机:Phase 6 各子阶段实现状态变化、验收结果更新时。
相关文档:
覆盖:
- Phase 6-1:演奏文件保存/打开(
.devpiano格式)。 - Phase 6-2:播放速度控制(0.5x–2.0x)。
- Phase 6-3:最近文件列表 + 拖拽打开。
- Phase 6-4:基础 MIDI 编辑(delete notes)。
- Phase 6-5:MIDI 导入增强(sustain CC64、pitch bend、program change)。
- Phase 6-7:MIDI / Performance 测试夹具与最小回归样本库(清单见
fixture-inventory.md)。
不覆盖:
- 完整工程文件(
.devpiano-project,Phase 7)。 - 多轨 / tempo map(Phase 7+)。
- Piano roll / 事件编辑器(Phase 8)。
- 旧 FreePiano
.fpm格式兼容。 - 测试框架自动化运行(Phase 8 之后)。
Phase 6-7 已建立一套固定 MIDI / Performance 测试夹具,位于 ../../tests/fixtures/。
完整 fixture 清单、目录结构、预期用途见 fixture-inventory.md。
所有 MIDI fixture 均通过 mido 库验证:
OK empty.mid: type=0 ppq=960 tracks=1 track0(1msg,0notes)
OK multitrack-basic.mid: type=1 ppq=960 tracks=2 track0(3msg,0notes) track1(8msg,6notes)
OK simple-notes.mid: type=0 ppq=960 tracks=1 track0(9msg,6notes)
OK sustain-pedal.mid: type=0 ppq=960 tracks=1 track0(10msg,6notes)
OK tempo-change-basic.mid: type=0 ppq=960 tracks=1 track0(7msg,4notes)
OK velocity-channel.mid: type=0 ppq=960 tracks=1 track0(34msg,32notes)
FAIL invalid.mid: MThd not found. Probably not a MIDI file ← 预期行为,证明非法文件识别正常
注:
invalid.mid的FAIL为预期行为,表示文件被正确识别为非 MIDI 文件。
- 在 Windows 镜像树中运行
DevPiano.exe。 - 构建验证优先使用:
./scripts/dev.sh wsl-build --configure-only
./scripts/dev.sh win-build- 如链接失败并提示
DevPiano.exe无法写入,先关闭正在运行的程序后重试。
- 新录制的 take:录制几秒电脑键盘演奏,用于保存测试。
- 已保存的
.devpiano文件:由本程序保存生成,用于打开测试。 - 损坏的
.devpiano文件:手动编辑 JSON 破坏结构,用于错误路径测试。 - 空 JSON 文件:内容为
{},用于边界测试。 - 非 JSON 文件:扩展名改为
.devpiano的文本文件,用于格式错误测试。
- 任意已录制或已导入的 take:用于播放速度测试。
- 多个
.devpiano文件:用于最近文件列表测试。 - 多个
.mid文件:用于混合最近列表测试。 - 用于拖拽测试的
.devpiano和.mid文件。
- 包含多个音符的录制 take:用于选中和删除测试。
- Fixture 文件(清单见
fixture-inventory.md):../../tests/fixtures/midi/sustain-pedal.mid(含 CC64 sustain)。../../tests/fixtures/midi/simple-notes.mid(回归测试基准)。
- 额外手工 MIDI 文件(可选):含 pitch bend、program change 的文件用于手工验证。
状态:✅ 已完成。
验收项:
- 有录制 take 且状态为 idle/stopped 时,Save 按钮 enabled。
- 无录制 take 时,Save 按钮 disabled。
- Recording 期间,Save 按钮 disabled。
- Playing 期间,Save 按钮 disabled。
- 点击 Save 按钮后 FileChooser 打开。
- FileChooser 默认文件名包含时间戳(如
performance-20260503-120000.devpiano)。 - FileChooser 默认目录为上次保存路径(首次为系统默认目录)。→ Phase 6-3
- 选择保存位置后,文件成功写入。
- 保存的文件可用文本编辑器打开,内容为合法 JSON。
- JSON 包含
version字段,值为1。 - JSON 包含
format字段,值为"devpiano-performance"。 - JSON 包含
sampleRate字段,值与录制时采样率一致。 - JSON 包含
lengthSamples字段,值与录制时长度一致。 - JSON 包含
events数组,事件数与录制 take 一致。 - 每个 event 包含
timestampSamples、source、midiData字段。 -
midiData字段为字节数组,可还原为juce::MidiMessage。 - 保存完成后 Logger 输出文件路径和事件数。
测试步骤:
- 启动 DevPiano。
- 点击 Record,用电脑键盘演奏几个音符(如 C-E-G-C),点击 Stop。
- 确认 Save 按钮 enabled。
- 点击 Save,FileChooser 打开。
- 确认默认文件名包含时间戳。
- 选择保存位置,确认保存。
- 用文本编辑器打开保存的文件,检查 JSON 结构。
- 确认 Logger 输出了文件路径和事件数。
验收项:
- 非录制/播放状态时,Open 按钮 enabled。
- Recording 期间,Open 按钮 disabled。
- Playing 期间,Open 按钮 disabled。
- 点击 Open 按钮后 FileChooser 打开,文件过滤器为
*.devpiano。 - FileChooser 默认目录为上次打开路径。→ Phase 6-3
- 选择有效的
.devpiano文件后,文件成功读取并解析。 - 打开后自动开始回放。
- 回放内容与原始录制一致(音符、时序)。
- 打开后 Logger 输出文件路径和事件数。
- 打开后 Export MIDI 按钮行为与录制 take 一致(可导出 MIDI)。
- 打开后 Export WAV 按钮可用。
测试步骤:
- 使用 4.1 中保存的
.devpiano文件。 - 启动 DevPiano(或先录制一段再停止,确保有初始状态)。
- 点击 Open,FileChooser 打开。
- 确认文件过滤器为
*.devpiano。 - 选择之前保存的文件,确认打开。
- 确认自动开始回放,音符内容与原始录制一致。
- 确认 Logger 输出了文件路径和事件数。
- Stop 后确认 Export MIDI 和 Export WAV 按钮可用。
状态:✅ 已完成(2026-05-04)。
验收项:
- ControlsPanel 显示当前播放速度,默认
1.00x。 -
-按钮可降低速度,最低到0.50x。 -
+按钮可提高速度,最高到2.00x。 - 速度到达边界时对应按钮 disabled(
setPlaybackSpeed()中同步更新setEnabled())。 - 播放中调整速度,回放速率立即变化。
- 0.50x 慢速播放时音符间隔明显拉长,无卡顿。
- 2.00x 快速播放时音符间隔明显缩短,无爆音。
- 每次启动默认 1.0x(不持久化速度值)。
- 打开
.devpiano文件或导入.mid文件后,速度控制同样生效。
状态:暂缓。
验收项:
- 打开
.devpiano文件后,最近文件列表更新。 - 导入
.mid文件后,最近文件列表更新。 - 最近文件列表最多显示 10 条。
- 同一文件重复打开时,列表中只保留一条(移到最前)。
- 点击最近文件列表项可打开对应文件。
- 列表中文件不存在时,点击后提示文件不存在并从列表移除。
- 最近文件列表在程序重启后仍然有效。
验收项:
- 拖拽
.devpiano文件到主窗口,触发打开演奏文件。 - 拖拽
.mid文件到主窗口,触发 MIDI 导入。 - 拖拽不支持的文件类型时,无反应或提示不支持。
- Recording / Playing 期间拖拽文件,忽略或提示。
状态:暂缓。
验收项:
- 打开或录制的演奏数据中,可选中单个音符。
- 选中的音符有视觉高亮。
- 按 Delete 键或点击 Delete 按钮可删除选中音符。
- 删除后回放不再包含该音符。
- 删除操作不破坏其他事件的时间线和顺序。
- 删除后可正常保存为
.devpiano文件,保存的文件不含已删除事件。 - 删除后可正常导出 MIDI 和 WAV。
状态:暂缓。
验收项:
- 导入
sustain-pedal.mid(含 CC64 sustain)后,回放时延音踏板效果可听(依赖插件支持)。 - 导入
simple-notes.mid仅包含 note on/off 的 MIDI 文件时,行为与 Phase 4 一致,无回退。 - Logger 输出导入的非 note 事件数量。
- 导入后保存为
.devpiano文件,非 note 事件正确保留。 - 打开包含非 note 事件的
.devpiano文件后,回放时非 note 事件正确还原。
Fixture 参考:
fixture-inventory.md—../../tests/fixtures/midi/sustain-pedal.mid、../../tests/fixtures/midi/simple-notes.mid
以下为 Phase 6 各子阶段实现后需持续关注的回归项:
- Phase 3 录制/回放/MIDI 导出/WAV 导出主链路不受影响。
- Phase 4 MIDI 导入/回放/自动选轨行为不受影响。
- Phase 4 导入 playback take 禁止 MIDI 再导出的边界不受影响。
- Phase 5 架构收敛后的模块职责边界不受影响。
- 插件加载/卸载/editor 窗口行为不受影响。
- 键盘映射和虚拟键盘显示行为不受影响。
- Performance Preset 保存/加载/恢复行为不受影响。