Cue 表,以及能当专辑听的整轨
如果你在拆轨成为默认做法之前就在抓 CD,那你媒体库里有一块是一碟一个 FLAC,旁边配一个文本文件。大多数播放器要么无视这个表,要么把它描述的东西当二等公民。下面是 rox 拿它怎么办。
cue 整轨到底是什么
一个装着整张碟的音频文件,加一个列出每首曲目从哪儿开始的 .cue 表。这是保存 CD 的准确做法,因为曲目之间的间隙也是这张碟的一部分,拆轨会把它们扔掉。它也打破了每个媒体库赖以成立的那个假设:一个文件就是一首曲目。
播放器解决这件事的方式有三种。无视这个表,显示一首七十分钟的曲目。显示表里的曲目,但把它们和真正的媒体库分开,于是搜索、排序和播放列表对它们的行为都不一样。或者把这个断口当真,一次性把它吸收掉。
真正的行,不是碎片
rox 把表里的每一段索引成媒体库里一个普通的行,用它的文件加它的曲目号来标识。下游的一切都把它读作一首曲目,因为对下游来说它就是。播放列表给它拍快照,收听记录挂在它上面,搜索找得到它,排序列也排得了它,而它们谁都不知道有十一行指向同一个 FLAC。
大多数实现选的另一条路是合成路径,album.flac#3,这让数据库看着整齐,也把问题推给了之后每一段要打开这个路径的代码。哪儿漏剥一次,就是一个静默 bug,从空气里读出标签字节。而一张 cue 表都没有的媒体库,为这一切付出的是零:这些分段住在一张副表里,热路径上没有任何东西会去读它们。
像播一个文件那样播一段
引擎把一段当成那首曲目的整个世界:精确定位到它的起点、两端做采样级的裁切,还有一个结束边界,走的和真正的文件末尾是同一条路。无缝、交叉淡化、播完即停和循环全都能用,不需要知道分段这回事。
头部裁切是关键的那个细节。精确定位落在包边界上而不是精确的采样点上,所以如果不丢掉落点到分段起点之间的那些帧,每首曲目开头都会带着上一首的尾巴。那就是没做完的 cue 实现听起来的样子。
同一个整轨里连续的曲目共用一个专辑分组,这让交叉淡化不会横跨一张碟自身的无缝接缝。一个整轨播起来就是它当初那张唱片的样子。
扫描,以及改主意
表会认领它的镜像。只要有 cue 列着某个文件,那个文件就不会有自己的行,所以不会出现十一首曲目再加一个七十分钟的重复项。新鲜度按两者中较晚修改的那个算,所以改表或者改音频,下次扫描都会重新切这个整轨。把表删掉,镜像就折回成普通的一行。
元数据优先用表,读不到就退回镜像自己的标签。在 UTF-8 还不成规矩的年代写的表会有 cp1252 兜底,因为老整轨恰恰就是这个功能面向的人群。
不会给整张碟盖同一个章的评分
rox 平时把评分写进文件本身,在一个文件就是一首曲目的时候这是对的。在 cue 整轨上就不对了:镜像属于全部十一首曲目,所以逐曲目写入会给它们每一首都盖上同样的星。
写入器对这些行拒绝写文件那一半,数值由数据库保管。标签编辑也一样。整轨上拿到的是逐曲目评分,而镜像出来时字节完全没变。
你要过一阵才会注意到的那些部分
- m3u 导出把分段写成
path#N,导入优先精确路径匹配,所以一份列表在别的软件里转一圈回来,不会塌成那个镜像。 - Scrobble 和“正在播放”按这一对去重,所以一张碟的十一首曲目 scrobble 成十一首,而不是一首很长的。
- 重新扫描后收听记录按段重新挂上,所以哪怕每一段带着完全相同的标签,一张碟也能保住逐曲目的播放历史。
- 只有 ReplayGain 的专辑那一对数值会带过来。针对整张碟镜像写的曲目数值描述的是这张碟,所以会被忽略而不是被采信。
衡量这件事的标准不是整轨能不能播。是一个月之后,媒体库里还有没有什么东西因为出自一个镜像而行为不同。
让它指向你从没拆过的那批
扫描器第一遍就会把这些表连同其他一切一起收进来。更多内容见规模上来之后什么会崩。