TiXL 中 MidiControlOutput 运算符完整指南:用 CC 与 Channel Pressure 事件实时驱动外部 MIDI 设备

原创2026-09-19 23:56:511,661 阅读
文章标签:音视频图形学桌面应用

TiXL 中 MidiControlOutput 运算符完整指南:用 CC 与 Channel Pressure 事件实时驱动外部 MIDI 设备

TiXL 的 MidiControlOutput 运算符(位于 Lib.io.midi 运算符库)用于向指定 MIDI 输出设备发送 7 位 Control Change(CC,控制器变化)或 Channel Pressure(通道压力/复音后触)事件,是搭建“可视化内容 ↔ 外部硬件/软件”双向控制链路的关键节点之一。读完本文,你将掌握该节点的全部输入输出参数、两种发送模式(连续发送与触发发送)的取舍、与 MidiNoteOutput 等节点组合驱动生成音乐或硬件的工作流,以及其背后的源码实现与设备管理机制。

一、MidiControlOutput 是什么

MidiControlOutput 是 TiXL 运算符库中负责 MIDI 输出 的节点,其核心能力是:

向选中的 MIDI 设备发送 7 位 ControlChange(控制器变化)或 Channel Pressure(通道压力)事件。值的输入方式有两种:整数(0~127)或归一化浮点数(0.0~1.0)。

在原文档的检索语义中,该节点常被称作:aftertouch、pressure、cc、controller,即它同时覆盖了“控制器变化(CC)”与“通道压力(aftertouch/pressure)”两类 MIDI 消息的发送需求。

它属于 Lib.io.midi 运算符家族的一员,该家族还包括:

文档明确建议:将 MidiControlOutput 与上述输出节点组合使用,可用于创造生成音乐(generative music)或控制硬件,例如用视觉参数实时驱动合成器、灯光控制器或 MIDI 映射软件。仓库中的官方示例 MidiNoteOutputExample.t3 正是同时实例化了 MidiNoteOutput、MidiPitchbendOutput 与 MidiControlOutput 三个输出节点,展示了三者协同驱动一台 MIDI 设备的完整接线方式。

二、快速开始:基础配置流程

在 TiXL 中新增一个 MidiControlOutput 节点后,最小可用配置如下:

  1. 选择设备(Device):该输入在节点 UI 上呈现为下拉菜单(Usage: "CustomDropdown"),选项中枚举了当前系统所有可用的 MIDI 输出设备。选项列表来自 MidiConnectionManager.cs 中维护的 MidiOutsWithDevices 字典(详见下文源码剖析)。
  2. 设置通道(ChannelNumber):目标 MIDI 通道,取值范围 1~16,UI 与源码均做了 Clamp 限制。
  3. 选择消息类型(CCorPressure):ContinuousController_CC(CC 消息)或 ChannelPressure(通道压力)。
  4. 指定控制器号(ControllerNumber):CC 模式下决定发送到哪一个控制器(如调制轮 1、音量 7、声像 10 等),取值范围 0~127。
  5. 给定数值(Value / ValueFloat):选择整数或浮点输入方式。
  6. 连接 Result 输出:将 Result(T3.Core.DataTypes.Command)连接到后续执行流(在示例中它被连接到 Execute 节点,随帧驱动发送逻辑)。

参考官方示例 MidiNoteOutputExample.t3:其中 MidiControlOutput 的 Device 被设置为 loopMIDI Port 1(一个常见的虚拟 MIDI 端口),SendMode 被设置为 1(即 SendWhenTriggered),TriggerSend 由 AnimValue 节点的触发输出驱动,Value 由 PerlinNoise 经过 FloatToInt 转换后喂入——这正是“用生成式动画参数实时调制 MIDI CC”的典型接线。

三、输入参数详解

原文档给出了输入参数的完整清单,下面结合仓库中的默认配置(MidiControlOutput.t3)与 UI 约束(MidiControlOutput.t3ui)逐一展开:

参数名 类型 默认值 说明
SendMode Int32 0 发送模式:0 = SendContinuously(连续发送)、1 = SendWhenTriggered(触发发送)
TriggerSend Boolean false 触发信号输入,仅在 SendWhenTriggered 模式下有意义
CCorPressure Int32 0 消息类型:0 = ContinuousController_CC、1 = ChannelPressure
Device String ""(空) 目标 MIDI 输出设备名,UI 呈现为下拉菜单
ChannelNumber Int32 1 目标 MIDI 通道,源码 Clamp 到 1~16
ControllerNumber Int32 1 CC 控制器号,UI/源码 Clamp 到 0~127
Value Int32 0 整数方式的值,UI/源码 Clamp 到 0~127
UseValueFloat Boolean false 是否改用浮点方式提供值
ValueFloat Single 0.0 归一化浮点值,UI/源码 Clamp 到 0.0~1.0

3.1 SendMode:两种发送模式

  • SendContinuously(连续发送):每个渲染帧向设备发送一次当前值。适用于需要持续跟踪参数变化的场景(如用鼠标 X 坐标连续控制灯光亮度)。
  • SendWhenTriggered(触发发送):仅在 TriggerSend 输入收到一个“临时的布尔 true 值”时发送一次。文档特别强调:连续发送可能淹没(flooding)接收设备,因此只要条件允许,应优先使用触发模式。

3.2 值输入:整数还是浮点

  • 当 UseValueFloat = false 时,读取 Value(整数),源码在 MidiControlOutput.cs 中将其 Clamp 到 0~127;
  • 当 UseValueFloat = true 时,读取 ValueFloat(浮点),先 Clamp 到 0.0~1.0,再乘以 127 并取整(见 MidiControlOutput.cs),即 intValue = (int)(ValueFloat * 127)。

两种方式最终都归一为 7 位 MIDI 值域(0~127),方便与动画曲线、随机值、鼠标/传感器输入等浮点信号直接对接。

3.3 消息类型:CC 与 Channel Pressure 的区别

  • ContinuousController_CC:发送标准的 Control Change 消息,需要指定 ControllerNumber(0~127)。MIDI CC 是最通用的参数控制方式,可映射到合成器参数、灯光台通道、VJ 软件控件等。
  • ChannelPressure:发送通道压力(Channel Aftertouch)事件,作用于整个通道,不需要控制器号,值域同样是 0~127。

四、降低负载的触发技巧

文档给出了一个非常实用的性能建议:

可以用 AnimValue 节点的 "WasHit" 输出作为 TriggerSend 的触发源,并将触发频率设置到对目标用途而言足够平滑的程度,从而降低发送负载。

其背后的原理是:TriggerSend 只有在从 false 跳变为 true 的边沿时才会触发一次发送(源码中的边沿检测见 MidiControlOutput.cs)。因此,用一个周期性的脉冲信号(如 AnimValue 的 WasHit)去驱动触发端,可以将“每帧发送”降级为“按所需频率发送”,显著减少对接收设备与系统资源的压力。同理,MidiPitchbendOutput 也提供了完全相同的连续/触发双模式设计。

五、输出与错误反馈

  • Result 输出:类型为 T3.Core.DataTypes.Command(见 MidiControlOutput.cs),用于接入执行流,驱动发送逻辑随帧求值。
  • 状态反馈:节点实现了 IStatusProvider 接口。当找不到 Device 中指定的设备时,会设置错误消息 Can't find MidiDevice {deviceName};发送异常时则记录 Failed to send midi to {deviceName}: ...(见 MidiControlOutput.cs),并在节点 UI 上以错误状态呈现,便于排查设备名拼写或设备被其他程序占用的问题。

六、源码实现剖析:消息是如何发出去的

6.1 生命周期与设备注册

MidiControlOutput 在第一次求值时调用 MidiConnectionManager.RegisterConsumer(this),将自己注册为 MIDI 消费者,以保持 MIDI 连接线程活跃(见 MidiControlOutput.cs);节点销毁时调用 UnregisterConsumer 释放资源。虽然本节点只发送、不接收,但注册消费者是让 MidiConnectionManager 完成设备扫描与连接初始化的前提(该类在 MidiConnectionManager.cs 中实现了首次扫描、消费者挂接等逻辑)。

6.2 设备匹配与消息构造

在每次更新时,节点遍历 MidiConnectionManager.MidiOutsWithDevices,按 ProductName 精确匹配 Device 输入指定的设备名(见 MidiControlOutput.cs)。根据消息类型与发送模式构造对应的 NAudio MIDI 事件对象:

  • CC 模式:new ControlChangeEvent(0, channel, (MidiController)controller, intValue)(MidiControlOutput.cs);
  • 通道压力模式:new ChannelAfterTouchEvent(0, channel, intValue)(MidiControlOutput.cs);
  • 触发模式下仅当 triggerJustActivated 为 true 时构造并发送,连续模式下每帧构造并发送。

最终通过 m.Send(midiEvent.GetAsShortMessage()) 将事件编码为 MIDI short message 写入输出设备(MidiControlOutput.cs)。

6.3 设备下拉菜单的实现

Device 下拉选项并非硬编码,而是通过实现 ICustomDropdownHolder 接口,动态枚举 MidiConnectionManager.MidiOutsWithDevices 中所有输出设备的 ProductName 实时生成(见 MidiControlOutput.cs),因此拔插设备后重新扫描即可刷新选项。设备枚举、占用检测(MemoryAllocationError 提示设备已被其他应用使用)以及 LimitMidiDeviceCapture 配置项过滤均集中在 MidiConnectionManager.cs。

七、典型实战场景

  1. 实时控制硬件/灯光:用鼠标、传感器或动画曲线驱动 ValueFloat,通过 CC 消息控制灯光亮度、色温或舞台设备参数,实现“视觉内容即控制器”。
  2. 生成音乐(Generative Music):与 MidiNoteOutput(音符)、MidiPitchbendOutput(弯音)、MidiTriggerOutput(音色切换与走带控制)组合,让 TiXL 的动画系统直接驱动合成器。官方示例 MidiNoteOutputExample.t3 展示了一条完整的生成式 MIDI 链路:PerlinNoise + AnimValue 产生参数 → FloatToInt 转整数 → 同时喂给 MidiNoteOutput 与 MidiControlOutput。
  3. 参数自动化/映射到 VJ 软件:将 TiXL 的输出桥接到 loopMIDI 等虚拟 MIDI 端口,再映射到 Ableton、Resolume 等外部软件的 MIDI 参数,实现跨软件联动。

八、注意事项

  • 设备占用:MIDI 输出设备同一时间通常只能被一个应用独占使用,若提示设备已被占用,需关闭其他占用程序或改用虚拟 MIDI 端口(如 loopMIDI)。
  • 消息洪泛:连续发送模式下每帧一条消息,对高速率运行(如高帧率)场景可能造成接收设备过载,应优先改用触发模式并配合 AnimValue 的 WasHit 控制发送频率。
  • 值的范围:无论整数还是浮点输入,最终都收敛到 0~127;通道号被限制在 1~16,控制器号被限制在 0~127,超出部分会被自动 Clamp。
  • 设备名精确匹配:Device 字符串必须与设备 ProductName 完全一致,否则节点会报 Can't find MidiDevice 错误;建议通过节点 UI 的下拉菜单选择以避免手误。

九、相关资源

登录后查看全文
t3