当抽象层失效:Behringer Wing混音器与DigiMixer的集成挑战
在我撰写上一篇DigiMixer预览文章时,我正等待最新混音器Behringer Wing Rack的到货。几天后它终于送达,我很高兴地宣布它很快就被集成到DigiMixer中(现在它是我的主力混音器)。虽然大部分集成过程都很顺利,但Wing有一个方面不太符合抽象模型——这使它成为博客文章的绝佳主题。
Wing的输出配置
在现实世界(与课程中通常给出的刻意示例相反),抽象层总是会有些许失效。世界并不像我们希望的那样整齐地装入盒子中。重要的是要区分故意有损的抽象和抽象中的实际"断裂"。有损抽象会忽略一些对我们使用抽象方式不重要的细节。例如,DigiMixer在混音器功能方面非常有损:它不尝试建模混音器的路由、可应用的任何效果器,或输入如何配置前置放大器增益、微调、立体声平衡等。这一切都很好,虽然Wing有很多功能未被抽象捕获,但这并不新鲜。
但当抽象无法表示我们关心的方面时,抽象就会断裂。在Wing的情况下,这就是主输出。让我们回顾一下我之前提到的关于通道的内容:
每个通道都有以下信息:
- 名称
- 推子电平(对于输入通道,这是"每个输出通道一个推子电平")。这可以由应用程序控制。
- 是否静音。这可以由应用程序控制。
- 表头信息(即当前输入和输出电平)
混音器都有输入和输出通道。有不同类型的输入和输出,但在大多数情况下,我们不需要区分这些"类型"。混音器可能会这样做,但DigiMixer不必如此。它确实有一个"主"输出的概念,假定为单个立体声输出通道。这有一对预设的通道ID(100和101),X-Touch Mini集成确实对此进行了特殊处理,假设顶部的旋转编码器应用于控制每个输入的主电平。但在大多数情况下,它只是一个普通通道,有自己的音量推子和静音。
插曲:音频信号的路径
我想花点时间确保我已经清楚地说明了音频路径的工作原理,至少是简单形式。假设我们有一个简单的混音器,有两个输入和一个主输出。暂时忘记单声道/立体声。
我们会有三个推子(用于控制音量的物理滑块):
- 输入1一个
- 输入2一个
- 输出一个
这意味着如果有人对着输入1的麦克风唱歌,而电吉他提供输入2,你可以:
- 通过调整输入推子来改变唱歌和吉他之间的平衡
- 通过调整输出推子来改变整体音量
还会有三个静音按钮:每个输入一个,输出一个。因此,如果麦克风开始产生反馈,你可以只静音它(但保留吉他可听),或者你可以用输出静音来静音所有内容。
如果我们有两个输出——称它们为"主"和"辅助"——那么逻辑上会有六个推子(在物理控制台上它们不太可能都是单独的滑块):
- 输入1馈送到主输出的信号一个
- 输入1馈送到辅助输出的信号一个
- 输入2馈送到主输出的信号一个
- 输入2馈送到辅助输出的信号一个
- 主输出一个
- 辅助输出一个
我们对不同输出的不同输入应用不同推子电平的能力是我教堂每周都在使用的:我们有一个麦克风拾取会众的歌声,以便通过Zoom发送…但我们根本不想在教堂建筑内放大它。同样,对于某人说话,我们可能在建筑内比在Zoom上放大得更多,或者反之。
DigiMixer建模静音的方式是,每个输入只有一个静音,每个输出只有一个静音,因此在我们的"两个输出"混音器上,我们会有六个推子,但只有四个静音。实际上,大多数混音器确实提供"每个输入,每个输出"的静音,但也有链接静音的概念,其中静音一个输入通道会静音所有输出。
但这一切的结果是,即使在我们简化的模型中,从输入到输出的音频信号也要经过包含两个推子和两个静音的路径:有多种方式可以调整音量或应用静音,取决于你想要做什么。
有了这些理解,让我们回到Wing…
Wing上的Main LR与Main 1-4
Behringer Wing有很多通道:48个立体声输入通道和20个立体声输出通道(加上矩阵混音和其他时髦的东西——我在这里简化了很多,坦白说,我离完全理解Wing的能力还有很长的路)。输出分为四个主通道(M1-M4)和16个总线通道(B1-B16)。每个输出都有一系列每个输入的推子,以及自己的整体输出推子和静音。到目前为止,一切顺利。
然后还有"Main LR"。
“Main LR"听起来应该是一个常规的立体声主输出通道,通道ID为100和101,没有问题。就每个输入有一个Main LR的推子而言,这很好用。
但Main LR本身实际上不是一个输出。它没有自己的"整体"推子或静音。它没有表头电平。你不能将它路由到任何地方。用推子调整的输入电平在也被输入到M1-M4的推子调整之前,应用于所有M1-M4。因此,如果你有一个从M1发送到扬声器的单个输入,你有三个推子可以用来调整它:
- 输入的Main LR推子
- 输入的M1推子
- 整体M1推子
在这种情况下有两个静音选项:
- 输入的静音
- M1的静音
DigiMixer中的Main LR
所有这些都可以在DigiMixer中表示——我们可以为Main LR添加一个"假"输出通道——确实这样做是有用的,作为用X-Touch Mini上的旋转编码器调整的"主要输入"推子。
但然后我们得到了三个我们不想要的东西,因为它们在混音器本身上没有表示:
- Main LR整体推子
- Main LR表头
- Main LR静音
抽象没有足够的细微差别来表示这一点——它没有"仅用于输入推子的输出通道"的概念。
这三个额外的部分最终在DigiMixer中显示为无用的用户界面元素。我不是UI设计师(我认为通过之前部分的截图我们已经确定了这一点),但即使我也知道足够多,以至于对什么都不做的UI元素感到反感。
处理断裂的抽象
希望我已经合理地解释了我之前描述的DigiMixer抽象如何最终对Wing不足。(如果没有,请留下评论,我会尝试使其更清晰。我怀疑在没有实际操作真实混音器的情况下,仅仅移动不同的推子看看会发生什么,从根本上理解它是棘手的。)
下一步大概是修复抽象,对吧?嗯,也许吧。我想出了三个选项,我认为这些可能合理地代表了大多数类似情况下可用的选项。
选项1:忽略它
UI很糟糕,有一个从不显示任何内容的表头,以及一个似乎可操作但实际上根本不调整混音器的推子和静音按钮。
但是…所有应该工作的UI元素都工作。这比完全缺少Main LR通道要好得多,这会减少功能。
我可以完全忽略这个问题。有时这绝对没问题——重要的是权衡抽象断裂的实际后果与处理它的成本。这就是考虑你对抽象将如何使用的了解程度很重要的地方。DigiMixer应用程序(复数,但都是我写的)是DigiMixer抽象的唯一消费者。除非其他人开始编写自己的应用程序(我想是可能的——都是开源的),否则我可以推理断裂的所有影响。
如果这是Noda Time,例如,那将是另一回事——人们将Noda Time用于各种事情。当然,这并不意味着Noda Time中暴露的抽象没有尖锐的角落。我可以用这些填充多篇博客文章——包括我如何考虑修复它们、兼容性问题等。
选项2:扩展抽象
使DigiMixer中的核心抽象更具信息性并不难。实际上只是更新从DetectMixerConfiguration返回的MixerChannelConfiguration以包含更多每通道细节的问题。至少,这将是起点:该信息然后将在"中间"层被消耗,并再次向上暴露给应用层。
我可以直接实现这个选项…但有一件事仍然困扰着我:可能还有另一个变化即将到来。扩展抽象以完美适应Wing可能会使以后的生活更困难,当有另一个混音器以某种稍微不同的方式破坏模型时。我宁愿等到有更多的点来画一条直线,如果你明白我的意思。
当然,“等待和观察"策略有一个风险和开放性问题:我等多久?如果我在六个月内没有看到类似的东西,我应该在那时"适当地完成工作"吗?也许一年?我等得越久,代码中的一些丑陋之处存在的时间就越长——但我越早停止等待,出现其他事情的机会就越高。
再次,这个时间方面在比DigiMixer重要得多的抽象中相当常见。成本通常也会上升:如果DigiMixer已经作为一组遵循语义版本控制的NuGet包发布,那么我要么必须尝试找出如何在不进行破坏性更改的情况下扩展抽象,要么升级到新的主版本。
选项3:接受泄漏性
目前,我选择在较低层次保留抽象断裂,并仅在应用层处理问题。我已经创建的WPF用户控件使得使用数据绑定来条件化静音和表头是否可见足够容易。推子稍微棘手一些,部分原因是DigiMixer应用程序的两种"模式”:按输出分组输入,或按输入分组输出。基本上,这最终是关于决定在集合中包含哪些推子。
下一个问题是如何用正确的信息初始化视图模型。这可以在配置文件中完成——但那会有与选项2相同的问题。相反,我完全弄脏了:当设置混音器视图模型时,代码知道硬件类型是什么(通过配置)。因此我们可以只说"如果是Behringer Wing,适当调整事物”:
|
|
这段代码违反了拥有抽象的首要目的。混音器视图模型不应该知道或关心它在与什么硬件对话!然而…然而,它有效。
我不建议这通常是正确的方法。但有时,这是一个实用的方法。这绝对是要小心的事情——如果我必须为下一个混音器添加类似的块,我会更加不情愿。这是我故意承担的技术债务的一个例子。我希望将来移除它——我希望将来有更多信息指导我转向选项2。目前,我会忍受它。
结论
我总是说我希望DigiMixer展示抽象的现实世界问题以及干净的一面。我没有期望在上篇文章之后这么快就得到如此清晰的例子。
在我的下一篇文章中——如果一切按计划进行——我将研究一个听起来简单但花了我很长时间才达到当前方法的设计挑战。我们将一起研究"单位",包括推子和表头。希望那会比这段落听起来更有趣…