【问题标题】:Why doesn't this simple CoreMIDI program produce MIDI output?为什么这个简单的 CoreMIDI 程序不产生 MIDI 输出?
【发布时间】:2012-05-13 15:01:12
【问题描述】:

这是一个发送 MIDI 数据的极其简单的 CoreMIDI OS X 应用程序。问题是它不起作用。它编译得很好,并且可以运行。它不报告任何错误,也不会崩溃。创建的源在 MIDI 监视器中可见。但是,没有 MIDI 数据输出

有人可以告诉我我在这里做错了什么吗?

#include <CoreMIDI/CoreMIDI.h>

int main(int argc, char *args[])
{
    MIDIClientRef   theMidiClient;
    MIDIEndpointRef midiOut;
    MIDIPortRef     outPort;
    char pktBuffer[1024];
    MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer;
    MIDIPacket     *pkt;
    Byte            midiDataToSend[] = {0x91, 0x3c, 0x40};
    int             i;

    MIDIClientCreate(CFSTR("Magical MIDI"), NULL, NULL,
                     &theMidiClient);
    MIDISourceCreate(theMidiClient, CFSTR("Magical MIDI Source"),
                     &midiOut);
    MIDIOutputPortCreate(theMidiClient, CFSTR("Magical MIDI Out Port"),
                         &outPort);

    pkt = MIDIPacketListInit(pktList);
    pkt = MIDIPacketListAdd(pktList, 1024, pkt, 0, 3, midiDataToSend);

    for (i = 0; i < 100; i++) {
        if (pkt == NULL || MIDISend(outPort, midiOut, pktList)) {
            printf("failed to send the midi.\n");
        } else {
            printf("sent!\n");
        }
        sleep(1);
    }

return 0;
}

【问题讨论】:

    标签: macos midi core-foundation coremidi


    【解决方案1】:

    您正在调用MIDISourceCreate 创建一个虚拟 MIDI 源。

    这意味着您的源将出现在其他应用的 MIDI 设置 UI 中,并且这些应用可以选择是否收听您的源。你的 MIDI 不会被发送到任何物理 MIDI 端口,除非其他一些应用程序碰巧在那里引导它。这也意味着您的应用程序无法选择它发送的 MIDI 去向。我假设这就是你想要的。

    The documentation for MIDISourceCreate 说:

    创建虚拟源后,使用 MIDIReceived 将 MIDI 消息从虚拟源传输到连接到虚拟源的任何客户端。

    所以,做两件事:

    • 删除创建输出端口的代码。你不需要它。
    • MIDISend(outPort, midiOut, pktList)更改为:MIDIReceived(midiOut, pktlist)

    这应该可以解决您的问题。

    那么输出端口有什么用?如果你想将你的 MIDI 数据指向一个特定的目的地——也许是一个物理 MIDI 端口——你不会创建一个虚拟 MIDI 源。而是:

    1. 调用MIDIOutputPortCreate()制作输出端口
    2. 使用MIDIGetNumberOfDestinations()MIDIGetDestination() 获取目的地列表并找到您感兴趣的目的地。
    3. 要将 MIDI 发送到一个目的地,请致电 MIDISend(outputPort, destination, packetList)

    【讨论】:

    • 一千年内我都不会想到使用 MIDIReceived 来发送 MIDI。感谢您的出色解释。
    • 如果您在 MIDI 设备驱动程序的上下文中考虑 MIDI 源会更有意义:在这种情况下,驱动程序接收到一些 MIDI(通过线路),并告诉 CoreMIDI它。虚拟 MIDI 源是后来添加的。
    • @sixohsix 这里更大的一点可能是“更仔细地阅读文档”。我也错过了,可能会再次错过,所以我能感受到你的痛苦。
    • 不要忘记,如果发送带有时间戳的 Midi 事件,您需要在任何可能的传出数据上发送它们 - 否则接收器/目的地可能会忽略时间戳为 0(因为您的接收器假设它们发生在过去)。
    • #rant MIDIReceived收到。让它沉入其中。即使它在虚拟源之前转发 MIDI,这也是一个糟糕的命名。幸运的是,我们现在有了 Swift 来解决所有这些文档/命名问题。 /s
    【解决方案2】:

    我只是把它留在这里供我自己参考。这是一个 100% 基于您的完整示例,但包括另一方(接收)、我的错误 C 代码和接受的答案的更正(当然)。

    #import "AppDelegate.h"
    
    @implementation AppDelegate
    
    @synthesize window = _window;
    
    #define NSLogError(c,str) do{if (c) NSLog(@"Error (%@): %u:%@", str, (unsigned int)c,[NSError errorWithDomain:NSMachErrorDomain code:c userInfo:nil]); }while(false)
    
    static void spit(Byte* values, int length, BOOL useHex) {
        NSMutableString *thing = [@"" mutableCopy];
        for (int i=0; i<length; i++) {
            if (useHex)
                [thing appendFormat:@"0x%X ", values[i]];
            else
                [thing appendFormat:@"%d ", values[i]];
        }
        NSLog(@"Length=%d %@", length, thing);
    }
    
    - (void) startSending {
        MIDIEndpointRef midiOut;
        char pktBuffer[1024];
        MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer;
        MIDIPacket     *pkt;
        Byte            midiDataToSend[] = {0x91, 0x3c, 0x40};
        int             i;
    
        MIDISourceCreate(theMidiClient, CFSTR("Magical MIDI Source"),
                         &midiOut);
        pkt = MIDIPacketListInit(pktList);
        pkt = MIDIPacketListAdd(pktList, 1024, pkt, 0, 3, midiDataToSend);
    
        for (i = 0; i < 100; i++) {
            if (pkt == NULL || MIDIReceived(midiOut, pktList)) {
                printf("failed to send the midi.\n");
            } else {
                printf("sent!\n");
            }
            sleep(1);
        }
    }
    
    void ReadProc(const MIDIPacketList *packetList, void *readProcRefCon, void *srcConnRefCon)
    {
        const MIDIPacket *packet = &packetList->packet[0];
    
        for (int i = 0; i < packetList->numPackets; i++)
        {
    
            NSData *data = [NSData dataWithBytes:packet->data length:packet->length];
            spit((Byte*)data.bytes, data.length, YES);
    
            packet = MIDIPacketNext(packet);
        }
    }
    
    - (void) setupReceiver {
        OSStatus s;
        MIDIEndpointRef virtualInTemp;
        NSString *inName = [NSString stringWithFormat:@"Magical MIDI Destination"];
        s = MIDIDestinationCreate(theMidiClient, (__bridge CFStringRef)inName, ReadProc,  (__bridge void *)self, &virtualInTemp);
        NSLogError(s, @"Create virtual MIDI in");
    }
    
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
        MIDIClientCreate(CFSTR("Magical MIDI"), NULL, NULL,
                         &theMidiClient);
        [self setupReceiver];
        [self startSending];
    
    }
    
    @end
    

    【讨论】:

      【解决方案3】:

      其他人正在跳过的一个小细节:MIDIPacketListAddtime 参数对于某些音乐应用很重要。

      这是一个如何检索它的示例:

      #import <mach/mach_time.h>
      MIDITimeStamp midiTime = mach_absolute_time();
      

      来源:Apple Documentation

      然后,应用于此处的其他示例:

      pktBuffer[1024];
      MIDIPacketList *pktList = (MIDIPacketList*)pktBuffer;
      MIDIPacket *pktPtr = MIDIPacketListInit(pktList);
      MIDITimeStamp midiTime = mach_absolute_time();
      Byte midiDataToSend[] = {0x91, 0x3c, 0x40};
      pktPtr = MIDIPacketListAdd(pktList, sizeof(pktList), pktPtr, midiTime, sizeof(midiDataToSend), midiDataToSend);
      

      【讨论】:

      • 如果您忘记正确设置时间参数,您可能会发现由真正知名的德国软件公司生产的真正知名的应用程序拒绝绘制您传入的 MIDI-CC 数据。
      【解决方案4】:

      考虑您自己的 midi 客户端创建应用程序可能会崩溃,或者发送 midi 的主机也可能崩溃。您可以通过检查客户端/目的地是否已经存在然后通过处理单例分配来更轻松地处理这个问题。当您的 Midi 客户端存在但无法正常工作时,这是因为您需要告诉 CoreMidi 您的服装制作客户端能够处理什么以及当主机发送客户端大量使用时间戳时它将有什么延迟(又名ableton 和其他) .

      在您的 .h 文件中

      #import <CoreMIDI/CoreMIDI.h>
      #import <CoreAudio/HostTime.h>
      
      @interface YourVirtualMidiHandlerObject : NSObject 
      
      @property (assign, nonatomic) MIDIClientRef midi_client;
      @property (nonatomic) MIDIEndpointRef outSrc;
      @property (nonatomic) MIDIEndpointRef inSrc;
      - (id)initWithVirtualSourceName:(NSString *)clientName;
      
      @end
      

      在您的 .m 文件中

      @interface YourVirtualMidiHandlerObject () {
         MIDITimeStamp midiTime;
         MIDIPacketList pktList;
      }
      @end
      

      您可以通过以下方式准备启动您的虚拟客户端 也在你的 .m 文件中

      @implementation YourVirtualMidiHandlerObject 
      
      // this you can call in dealloc or manually 
      // else where when you stop working with your virtual client
      -(void)teardown {
          MIDIEndpointDispose(_inSrc);
          MIDIEndpointDispose(_outSrc);
          MIDIClientDispose(_midi_client);
      }
      
      - (id)initWithVirtualSourceName:(NSString *)clientName { 
          if (self = [super init]) {
              OSStatus status = MIDIClientCreate((__bridge CFStringRef)clientName, (MIDINotifyProc)MidiNotifyProc, (__bridge void *)(self), &_midi_client);
      
              BOOL isSourceLoaded = NO;
              BOOL isDestinationLoaded = NO;
      
              ItemCount sourceCount = MIDIGetNumberOfSources();
              for (ItemCount i = 0; i < sourceCount; ++i) {
                  _outSrc = MIDIGetSource(i);
                  if ( _outSrc != 0 ) {
                      if ([[self getMidiDisplayName:_outSrc] isEqualToString:clientName] && !isSourceLoaded) {
                          isSourceLoaded = YES; 
                          break; //stop looping thru sources if it is existing
                      }
                  }
              }
      
              ItemCount destinationCount = MIDIGetNumberOfDestinations();
              for (ItemCount i = 0; i < destinationCount; ++i) {
                  _inSrc = MIDIGetDestination(i);
                  if (_inSrc != 0) {
                      if ([[self getMidiDisplayName:_inSrc] isEqualToString:clientName] && !isDestinationLoaded) {
                          isDestinationLoaded = YES;
                          break; //stop looping thru destinations if it is existing
                      }
                  }
              }
      
              if(!isSourceLoaded) {
                  //your costume source needs to tell CoreMidi what it is handling
                  MIDISourceCreate(_midi_client, (__bridge CFStringRef)clientName, &_outSrc);
                  MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyMaxTransmitChannels, 16);
                  MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsProgramChanges, 1);
                  MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsNotes, 1);
                  // MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsClock, 1);
                  isSourceLoaded = YES;
              }
      
              if(!isDestinationLoaded) {
                  //your costume destination needs to tell CoreMidi what it is handling
                  MIDIDestinationCreate(_midi_client, (__bridge CFStringRef)clientName, midiRead, (__bridge void *)(self), &_inSrc);
                  MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyAdvanceScheduleTimeMuSec, 1); // consider more 14ms in some cases
                  MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesClock, 1);
                  MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesNotes, 1);
                  MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesProgramChanges, 1);
                  MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyMaxReceiveChannels, 16);
                  // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesMTC, 1);
                  // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesBankSelectMSB, 1);
                  // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesBankSelectLSB, 1);
                  // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertySupportsMMC, 1);
                  isDestinationLoaded = YES;
              }
      
              if (!isDestinationLoaded || !isSourceLoaded) {
                  if (status != noErr ) {
                      NSLog(@"Failed creation of virtual Midi client \"%@\", so disposing the client!",clientName);
                      MIDIClientDispose(_midi_client);
                  }
              }
          }
          return self;
      }
      
      // Returns the display name of a given MIDIObjectRef as an NSString
      -(NSString *)getMidiDisplayName:(MIDIObjectRef)obj {
          CFStringRef name = nil;
          if (noErr != MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &name)) return nil;
          return (__bridge NSString *)name;
      }
      

      对于那些试图在创建过程中读取速度(midi 传输)并为虚拟目的地设置属性的人... 不要忘记时间戳随数据包一起发送,但一个数据包可以包含多个相同类型的命令,甚至是多个时钟命令。在构建时钟计数器以查找 bpm 速度时,您必须考虑在计算之前至少计算其中的 12 个。当您只使用其中 3 个时,您实际上是在测量自己的缓冲区读取处理延迟,而不是真正的时间戳。 如果 midi 发件人未能正确设置时间戳,您的读取过程(回调)将处理时间戳...

      void midiRead(const MIDIPacketList * pktlist, void * readProcRefCon, void * srcConnRefCon) {
      
          const MIDIPacket *pkt = pktlist->packet;
      
          for ( int index = 0; index < pktlist->numPackets; index++, pkt = MIDIPacketNext(pkt) ) {
              MIDITimeStamp timestamp = pkt->timeStamp;
              if ( !timestamp ) timestamp = mach_absolute_time();
              if ( pkt->length == 0 ) continue;
      
              const Byte *p = &pkt->data[0];
              Byte functionalDataGroup = *p & 0xF0;
      
              // Analyse the buffered bytes in functional groups is faster
              // like 0xF will tell it is clock/transport midi stuff
              // go in detail after this will shorten the processing
              // and it is easier to read in code
              switch (functionalDataGroup) {
                  case 0xF : {
                         // in here read the exact Clock command
                         // 0xF8 = clock
                     }
                     break;
                  case ... : {
                         // do other nice grouped stuff here, like reading notes
                     }
                     break;
                  default  : break;
              }
          }
      }
      

      不要忘记客户端需要一个回调来处理内部通知。

      void MidiNotifyProc(const MIDINotification* message, void* refCon) {
      
          // when creation of virtual client fails we despose the whole client
          // meaning unless you need it you can ignore added/removed notifications
          if (message->messageID != kMIDIMsgObjectAdded &&
              message->messageID != kMIDIMsgObjectRemoved) return;
      
          // reactions to other midi notications you gonna trigger here..
      }
      

      然后你可以发送midi ...

      -(void)sendMIDICC:(uint8_t)cc Value:(uint8_t)v ChZeroToFifteen:(uint8_t)ch {
      
      
          MIDIPacket *packet = MIDIPacketListInit(&pktList);
          midiTime = packet->timeStamp;
      
          unsigned char ctrl[3] = { 0xB0 + ch, cc, v };
          while (1) {
              packet = MIDIPacketListAdd(&pktList, sizeof(pktList), packet, midiTime, sizeof(ctrl), ctrl);
              if (packet != NULL) break;
              // create an extra packet to fill when it failed before
              packet = MIDIPacketListInit(&pktList);
          }
      
          // OSStatus check = // you dont need it if you don't check failing
          MIDIReceived(_outSrc, &pktList);
      }
      

      【讨论】:

      • postet 因为所有其他答案都无法处理所提出的问题。在填充任何缓冲区之前,需要正确设置 CoreMidi。如果 CoreMidi 不知道您的虚拟 Midi 客户端有什么延迟和属性,它会显示但不会工作。
      猜你喜欢
      • 2013-03-10
      • 1970-01-01
      • 1970-01-01
      • 2016-07-15
      • 2019-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多