好的,所以我写了这个小程序。运行良好(一旦您授予它在系统偏好设置>安全和隐私>隐私>辅助功能>允许下面的应用程序控制您的计算机中生成关键事件的权利)。 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;
}