TiXL 中 MidiControlOutput 运算符完整指南:用 CC 与 Channel Pressure 事件实时驱动外部 MIDI 设备
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 运算符家族的一员,该家族还包括:
- MidiNoteOutput —— 发送 MIDI 音符(含 NoteOn/NoteOff)
- MidiPitchbendOutput —— 发送 14 位弯音(pitchbend)事件
- MidiTriggerOutput —— 发送 ProgramChange 与 Sequencer 开始/停止/继续命令
- MidiInput —— MIDI 输入侧节点
文档明确建议:将 MidiControlOutput 与上述输出节点组合使用,可用于创造生成音乐(generative music)或控制硬件,例如用视觉参数实时驱动合成器、灯光控制器或 MIDI 映射软件。仓库中的官方示例 MidiNoteOutputExample.t3 正是同时实例化了 MidiNoteOutput、MidiPitchbendOutput 与 MidiControlOutput 三个输出节点,展示了三者协同驱动一台 MIDI 设备的完整接线方式。
二、快速开始:基础配置流程
在 TiXL 中新增一个 MidiControlOutput 节点后,最小可用配置如下:
- 选择设备(Device):该输入在节点 UI 上呈现为下拉菜单(
Usage: "CustomDropdown"),选项中枚举了当前系统所有可用的 MIDI 输出设备。选项列表来自 MidiConnectionManager.cs 中维护的MidiOutsWithDevices字典(详见下文源码剖析)。 - 设置通道(ChannelNumber):目标 MIDI 通道,取值范围 1~16,UI 与源码均做了 Clamp 限制。
- 选择消息类型(CCorPressure):
ContinuousController_CC(CC 消息)或ChannelPressure(通道压力)。 - 指定控制器号(ControllerNumber):CC 模式下决定发送到哪一个控制器(如调制轮 1、音量 7、声像 10 等),取值范围 0~127。
- 给定数值(Value / ValueFloat):选择整数或浮点输入方式。
- 连接 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。
七、典型实战场景
- 实时控制硬件/灯光:用鼠标、传感器或动画曲线驱动
ValueFloat,通过 CC 消息控制灯光亮度、色温或舞台设备参数,实现“视觉内容即控制器”。 - 生成音乐(Generative Music):与
MidiNoteOutput(音符)、MidiPitchbendOutput(弯音)、MidiTriggerOutput(音色切换与走带控制)组合,让 TiXL 的动画系统直接驱动合成器。官方示例 MidiNoteOutputExample.t3 展示了一条完整的生成式 MIDI 链路:PerlinNoise+AnimValue产生参数 →FloatToInt转整数 → 同时喂给MidiNoteOutput与MidiControlOutput。 - 参数自动化/映射到 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 的下拉菜单选择以避免手误。
九、相关资源
- 运算符库索引:Lib.io.midi
- 同族输出节点文档:MidiNoteOutput、MidiPitchbendOutput、MidiTriggerOutput
- 节点实现源码:MidiControlOutput.cs
- 节点默认配置:MidiControlOutput.t3
- 节点 UI 定义:MidiControlOutput.t3ui
- MIDI 设备管理核心:MidiConnectionManager.cs
- 官方组合示例:MidiNoteOutputExample.t3