macos - 在 macOS 上将 MIDI 控制输入转换为虚拟按键(或虚拟 USB 按钮等)
问题描述
我想使用 MIDI 控制设备(如https://www.korg.com/us/products/computergear/nanokontrol2/)为各种软件生成控制输入,尤其是 Blender。
一种方法显然是将 MIDI 输入处理添加到 Blender 中。向 Blender 添加低级代码来监听 MIDI 按钮和滑块一点也不难,我基本上已经实现了。(即,我在 Blender 的最低级别添加了一个新的输入“类”,MIDI。)但是将其连接到现有的键盘和鼠标管道,尤其是 UI 功能以将功能与输入相关联要复杂得多,这不是我想要的现在潜入。
另一种方法可能是运行一些单独的软件来监听 MIDI 事件并将其转换为虚拟击键。假设可以生成比任何键盘上的实际键更多的击键种类,这可以很好地工作(例如,生成与真正键盘所没有的各种 Unicode 块相对应的击键)。这听起来可行吗?实现这种虚拟击键生成我应该考虑使用 a11y API 吗?这种方式的好处是它可以与任何软件一起使用。
或者有人有更好的主意吗?
解决方案
好的,所以我写了这个小程序。运行良好(一旦您授予它在系统偏好设置>安全和隐私>隐私>辅助功能>允许下面的应用程序控制您的计算机中生成关键事件的权利)。MIDI 音符开和关事件以及 MIDI 控制器值更改生成 macOS 按键,以 CJK 统一表意文字作为字符。
但是,后来我看到 Blender 是一种认为 ASCII 对每个人都应该足够的软件。换句话说,Blender 有硬编码限制,它处理的唯一键基本上是英文键盘上的键。您甚至无法将西里尔文或希腊语键(毕竟存在实际键盘)绑定到 Blender 功能,更不用说 CJK 键了。叹。回到绘图板。
/* -*- Mode: ObjC; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; fill-column: 150 -*- */
#import <array>
#import <cassert>
#import <cstdio>
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <CoreMidi/CoreMidi.h>
static constexpr bool DEBUG_MIDI2KBD(true);
static constexpr int BASE_KEYCODE(1000);
static CGEventSourceRef eventSource;
static std::array<unsigned char, 16*128> control;
static void NotifyProc(const MIDINotification *message, void *refCon)
{
}
static void sendKeyDownOrUpEvent(int character, int velocity, bool down) {
CGEventRef event = CGEventCreateKeyboardEvent(eventSource, character + BASE_KEYCODE, down);
// We send CJK Unified Ideographs characters
constexpr int START = 0x4E00;
assert(character >= 0 && character <= 20989);
const UniChar string[1] = { (UniChar)(START + character) };
CGEventKeyboardSetUnicodeString(event, 1, string);
CGEventPost(kCGAnnotatedSessionEventTap, event);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MIDIClientRef midi_client;
OSStatus status = MIDIClientCreate((__bridge CFStringRef)@"MIDI2Kbd", NotifyProc, nullptr, &midi_client);
if (status != noErr) {
fprintf(stderr, "Error %d while setting up handlers\n", status);
return 1;
}
eventSource = CGEventSourceCreate(kCGEventSourceStatePrivate);
control.fill(0xFF);
ItemCount number_sources = MIDIGetNumberOfSources();
for (int i = 0; i < number_sources; i++) {
MIDIEndpointRef source = MIDIGetSource(i);
MIDIPortRef port;
status = MIDIInputPortCreateWithProtocol(midi_client,
(__bridge CFStringRef)[NSString stringWithFormat:@"MIDI2Kbd input %d", i],
kMIDIProtocol_1_0,
&port,
^(const MIDIEventList *evtlist, void *srcConnRefCon) {
const MIDIEventPacket* packet = &evtlist->packet[0];
for (int i = 0; i < evtlist->numPackets; i++) {
// We expect just MIDI 1.0 packets.
// The words are in big-endian format.
assert(packet->wordCount == 1);
const unsigned char *bytes = reinterpret_cast<const unsigned char *>(&packet->words[0]);
assert(bytes[3] == 0x20);
if (DEBUG_MIDI2KBD)
printf("Event: %02X %02X %02X\n", bytes[2], bytes[1], bytes[0]);
switch ((bytes[2] & 0xF0) >> 4) {
case 0x9: // Note-On
assert(bytes[1] <= 0x7F);
sendKeyDownOrUpEvent((bytes[2] & 0x0F) * 128 + bytes[1], bytes[0], true);
break;
case 0x8: // Note-Off
assert(bytes[1] <= 0x7F);
sendKeyDownOrUpEvent((bytes[2] & 0x0F) * 128 + bytes[1], bytes[0], false);
break;
case 0xB: // Control Change
assert(bytes[1] <= 0x7F);
const int number = (bytes[2] & 0x0F) * 128 + bytes[1];
if (control.at(number) != 0xFF) {
int diff = bytes[0] - control.at(number);
// If it switches from 0 to 127 or back, we assume it is not really a continuous controller but
// a button.
if (diff == 127)
diff = 1;
else if (diff == -127)
diff = -1;
if (diff > 0) {
for (int i = 0; i < diff; i++) {
// Send keys indicating single-step control value increase
sendKeyDownOrUpEvent(16*128 + number * 2, diff, true);
sendKeyDownOrUpEvent(16*128 + number * 2, diff, false);
}
} else if (diff < 0) {
for (int i = 0; i < -diff; i++) {
// Send key indicating single-step control value decrease
sendKeyDownOrUpEvent(16*128 + number * 2 + 1, -diff, true);
sendKeyDownOrUpEvent(16*128 + number * 2 + 1, -diff, false);
}
}
}
control.at(number) = bytes[0];
break;
}
packet = MIDIEventPacketNext(packet);
}
});
if (status != noErr) {
fprintf(stderr, "Error %d while setting up port\n", status);
return 1;
}
status = MIDIPortConnectSource(port, source, nullptr);
if (status != noErr) {
fprintf(stderr, "Error %d while connecting port to source\n", status);
return 1;
}
}
CFRunLoopRun();
}
return 0;
}
推荐阅读
- python - 无法解决 python numpy zeros 中的内存错误
- android - 如何在播放时下载视频,使用 ExoPlayer?
- python - 安装 OpenVino 生成与张量流相关的错误
- java - notifyObservers 后连接异常
- android - 更改区域设置不会更改字体样式
- matlab - Matlab中if-else条件下向矩阵添加元素的问题
- c++ - g++生成没有push和pop的汇编代码
- vue.js - 尝试执行“vue create”时出现“错误命令失败:npm install --loglevel 错误”
- redirect - 如何保持重复的重定向历史
- c# - 无法使用实体框架模型删除数据行