【问题标题】:Turn MIDI control input into virtual keystrokes (or virtual USB buttons etc) on macOS在 macOS 上将 MIDI 控制输入转换为虚拟按键(或虚拟 USB 按钮等)
【发布时间】:2021-08-26 03:36:42
【问题描述】:

我想使用 MIDI 控制设备(例如 https://www.korg.com/us/products/computergear/nanokontrol2/)为各种软件生成控制输入,尤其是 Blender。

一种方法显然是将 MIDI 输入处理添加到 Blender 中。向 Blender 添加低级代码来监听 MIDI 按钮和滑块一点也不难,我基本上已经实现了。 (即,我在 Blender 的最低级别添加了一个新的“类”输入,MIDI。)但是将其连接到现有的键盘和鼠标管道,尤其是 UI 功能以将功能与输入相关联要复杂得多,这不是我想要的现在开始。

另一种方法可能是运行一些单独的软件来监听 MIDI 事件并将其转换为虚拟击键。假设可以生成比任何键盘上的实际键更多种类的击键,这可能会很好地工作(例如,生成与真正键盘所没有的各种 Unicode 块相对应的击键)。这听起来可行吗?实现这种虚拟击键生成我应该考虑使用 a11y API 吗?这种方式的好处是它可以与任何软件一起使用。

或者有人有更好的主意吗?

【问题讨论】:

    标签: macos blender midi


    【解决方案1】:

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

    【讨论】:

      猜你喜欢
      • 2012-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-29
      • 1970-01-01
      • 2010-12-31
      • 2023-03-10
      相关资源
      最近更新 更多