TL;DR- 我做了一个 DJ 混音应用。你可以在以下网址找到网页版fraktur.hya.io。
你们中有些人可能知道,大约 15-20 年前我经常在派对和音乐节上做 DJ。出于某种随机的原因,我最近又关注起了 DJ 软件领域,让我告诉你,感觉和 2008 年不一样了。我以前喜欢的一些应用已经停更;另一些还在,但它们的母公司面临倒闭风险。大多数现在都要求按月订阅费和网络连接,有着面向年轻受众的街头/都市审美,还动不动就想卖你 beat 包。我确信这是唯一合理的商业模式在这个经常性收入经济里,而且我并不真的在抨击它们(除了那种都市氛围和 beat 包,拜托),但说实话,我懒得折腾。
(还有一个开源应用叫Mixxx。它不太适合我,但如果你想要一个免费、久经考验的 DJ 应用,可以考虑它。)
所以,在这个个人软件的黄金时代,我决定用 Web Audio API 和 Electron(外加一点 Rust)打造我自己的 DJ 混音应用,我确信这会被视为一个坏主意,并会让人们非常愤怒。为我辩护一下,如今带Audio Worklets的 Web Audio API 相当高效(ScriptProcessorNode的时代已经过去了),而且在 deck 数量固定的情况下,你可以限制内存消耗,为每个 deck 分配一条音轨的音频缓冲区,并在它播放完毕后驱逐其缓冲区(后面会详细说)。
进一步辩护一下,这个应用是供个人使用的。我想我永远不会让自己经历正确分发和支持它的痛苦,所以没人能安装它并抱怨说,如果我当初用 C99 写,它会占用更少内存。
但是嘿,既然 Electron 是一个 Web 应用,而且IndexedDB有很好的存储能力,并且我们能够把 Rust 编译成 WASM 已经有一段时间了,那么从代码中提取出一个完全基于 Web 的应用并不难。这个 Web 应用功能齐全,可以在以下网址找到fraktur.hya.io。
(顺便说一句:Fraktur 是对我年轻时作为开发者创建的第一个 VST 插件的谐音梗,它有着浓重的blackletterlogo,同时也是对 2000 年代末音乐应用场景的一个双关(那时我们会在柏林泡夜店,每个音色好听的插件都有一个德语名字)
简化版 DJ 混音应用的工作原理
前提:我这里特指俱乐部 DJ,也就是站在舞池旁的控台后面、为其他人放音乐跳舞或甩头的人。俱乐部 DJ 的主要任务是拿两首速度相近的曲目,通过改变后一首曲目的播放速度并对齐底鼓来将它们同步,这个过程叫做对拍。两首曲目对拍完成后,就可以长时间一起播放并自然地互相淡入淡出,其理念是在它们之间无缝过渡,让人们不间断地跳舞。你可以用很多技巧让这个过程更复杂,但大致要点就是这些。
根据我的经验,DJ 混音软件主要有两种:一种是在某个时刻按下“播放”按钮实时启动曲目 B,让 B 叠在 A 上播放;另一种不太常见,是预先将曲目 B 锚定到曲目 A 上,就像DAW中的音频轨道那样。我年轻时在 DAW 里花了很多时间,所以个人更喜欢第二种,Fraktur 正是围绕这个前提构建的。
播放头
DAW 和 DJ 混音应用的主要区别在于,DAW 通常只有一个播放头,而 DJ 混音应用有两个:一个用于实时播放,一个用于预听。实时播放头只向前移动:它是舞池听到的不间断音乐流。预听播放头对应 DJ 在耳机里听到的内容,可以随意移动,以便提前监听混音。
因此,使用混音软件的 DJ 需要一个至少带两路立体声输出的音频接口,或者两个各带一路立体声输出的音频接口,一个专用于实时播放头,一个用于耳机预听输出(过去有一些非常低保真的方案,会把实时输出路由到右声道、预听输出路由到左声道,牺牲立体声效果,但技术发展已使这些方案过时)。Fraktur 需要两路输出才能现场工作,但 Web 应用启动时处于仅预听模式,因此你可以在不配置输出的情况下学习操作。
请注意,存在Mac 上 Chromium 的一个 bug它让你无法通过 Web Audio 访问大于 2 的声道,该问题应在版本 156 中修复,因此你可能需要更新浏览器。Safari 和 Firefox 似乎尚不支持显式路由。在所有这些情况下,你至少仍能使用预听模式。
(顺便说一句,如果你没有高级声卡,只想录制你的混音,像Blackhole这样的 Mac 项目可以让你创建一个虚拟声卡,用作实时输出。)
卡座
Fraktur 有 4 个卡座。一个卡座可以容纳一首曲目。你可以把曲目拆分成片段,并复制粘贴到各处,以便在混音过程中重复某个段落。这仍然算作一首位于卡座中的曲目。当实时播放头经过一首曲目的最后一个片段时,其卡座会自动清空,准备容纳下一首。
卡座按顺序使用。一次会话开始时,DJ 将一首曲目加载到卡座 A 并开始实时播放,然后将另一首曲目加载到卡座 B,并使用预听输出准备混音。随后 DJ 可以再加载一首曲目,在卡座 C 中准备下一次混音,再下一首则在卡座 D 中准备。在此过程中的某个时刻,实时播放头会到达卡座 A 中曲目的末尾,该卡座会自动清空,准备被重新使用,以此类推。这保证了任意时刻最多有 4 首曲目被加载到混音器中,从而使 RAM 消耗得到良好控制(解码后的音频曲目在作为音频缓冲区加载时非常庞大))。这也让 Fraktur 成为一种仅限现场使用的工具:你已经播放过的曲目及其所有自动化,一旦不再需要就会自动消失。这不是那种你把所有曲目依次排开、随时可以回到开头的 DAW。它更像是一组唱盘,你播放完一张唱片就把它拿走,为下一张腾出空间。这契合我的工作流程,让我能很好地专注于这门手艺的现场性,但给了我一个 4 轨的规划视野,而且我仍然可以修改到最后一秒。
自动化与片段
Fraktur 支持自动化,几乎和 DAW 一样。你可以通过在轨道波形上绘制自动化段来自动化音量、低频、中频、高频和混响。速度也可以通过右键单击时间标尺并设置速度事件来自动化。这些控件大致相当于一台带有均衡器旋钮和一个连接到混响效果的发送旋钮的 DJ 混音台。路线图上的下一个大事件,如果我能弄到一个形状合适的 MIDI 控制器,就是把应用连接到Web MIDI API并让用户能够绕过选定的自动化,通过实际现场推动推子和旋钮来进行混音。
Fraktur 还支持拆分、复制、剪切和粘贴曲目的片段,并在原曲目所在的 deck 内以任意顺序排列它们。
节拍匹配
最后,Fraktur 如何对两首速度不同的曲目进行节拍匹配?它又如何测量一首小节长度会漂移的曲目的速度——比如许多由不跟节拍器演奏的人类鼓手参与的非电子曲目?我选择的方案是自动检测节拍,用启发式方法提取最佳的“速度”估计,并在播放时通过扭曲偏离估计值的段落,将曲目调整到该速度。当然,这并非一个完美的过程,所以我编写了软件的另一部分——Ingestor,专门用于分析曲目、修正速度估计、移动检测到的节拍,甚至在别无他法时以曲目原始速度播放。
更详细地说,Fraktur 在目录或单首曲目被添加到应用后立即执行自动分析。它首先解码曲目并运行Beat This的mel-频谱图和节拍检测模型,这些模型运行在一个 Rust 分析器中,并编译为 WASM 以供浏览器版本使用。该过程返回节拍和强拍位置,Fraktur 会将这些位置与音频中的尖锐起音进行核对。随后 Ingestor 将这些观测结果拟合到一个整体速度上,丢弃游离的检测结果,并构建一个均匀网格或一组锚点,以应对时序漂移的情况。在同步播放期间,网格将每个节拍映射回录音中的位置;Signalsmith Stretch会加速或减慢音频以跟随混音速度,同时不改变其音高。在许多情况下(例如电子生成的曲目),网格是完全均匀的,无需任何修正。
在其他情况下,有些节拍会比整体速度所暗示的更短或更长。当鼓手短暂漂移,把某一段演奏得比前一段更快或更慢时,或者当模型混淆,在没有节拍的地方检测到节拍时,就会出现这种情况。在这种情况下,warp grid 会尝试通过让这些节拍比正常更快或更慢地播放来纠正它们,对于轻微的偏差这是察觉不到的,但对于更明显的偏差就会产生明显、刺耳的失真。在这些情况下,Ingestor 允许你手动移动、添加或删除检测到的节拍。
如果所有方法都不奏效,而轨道确实没有连贯的速度,Fraktur 有一个“Original”模式,在该模式下轨道按原样播放,不进行 warp-grid 速度校正,用户可以在这条轨道中固定一个节拍,并手动拉伸或收缩整条轨道,以改变其整体长度,并使其与正在混音的另一条轨道对齐,这个过程类似于“模拟”DJ 用变速唱机和唱片所做的操作。
Web 应用
你可以在此处找到该应用的 Web 版本fraktur.hya.io。添加一个目录,或将音频轨道拖放到页面上的任意位置,Fraktur 就会自动开始分析音乐。当一条轨道完成分析后,它就可以用于混音了。Fraktur 会通过哈希记住轨道,因此每条轨道只会被索引和分析一次。
请注意,桌面应用将速度数据存储在音乐库内的隐藏目录中,而浏览器版本则将其存储在 IndexedDB 中,以轨道哈希为索引。你需要自行检查IndexedDB 存储配额,不过它应该相当宽裕。另请注意,Fraktur 不会将轨道直接存储在 IndexedDB 中,因此它在访问新目录之前总是会请求权限。