获取使用 Logic 生成的 midi 序列 (MusicSequence) 标记

Posted

技术标签:

【中文标题】获取使用 Logic 生成的 midi 序列 (MusicSequence) 标记【英文标题】:Get midi sequence (MusicSequence) markers generates with Logic 【发布时间】:2013-08-16 14:40:15 【问题描述】:

我正在开发一个应用程序,它可以播放带有音频单元的 midi 序列 (.mid)。 midi 文件是使用 Logic 创建的,它提供了在时间线上添加标记的可能性。

在代码中,我使用 MusicSequence MusicPlayer 读取文件,并使用 MIDIClientCreate MIDIDestinationCreate 解析 MIDI 数据包。

主要方法

    OSStatus result = noErr;


// Initialise the music sequence
NewMusicSequence(&_s);

// Get a string to the path of the MIDI file which
// should be located in the Resources folder
NSString *midiFilePath = [[NSBundle mainBundle]
                          pathForResource:@"mymidifile"
                          ofType:@"mid"];

// Create a new URL which points to the MIDI file
NSURL * midiFileURL = [NSURL fileURLWithPath:midiFilePath];

// Load the file
MusicSequenceFileLoad(_s, (__bridge CFURLRef) midiFileURL, 0, 0);



// Initialise the music player
NewMusicPlayer(&_p);


// Load the sound from EXS file
[self loadFromEXS:@"Grand Piano" withSampler:_samplerUnit];

//Load Click
[self loadFromSoundFont:@"hit set" withSampler:_samplerUnit2];


//Assign channel to tracks
MusicTrack track = NULL;
MusicTrack track2 = NULL;
MusicSequenceGetIndTrack(_s, 1, &track);
MusicSequenceGetIndTrack(_s, 2, &track2);

//Assign tracks to audio units
MusicTrackSetDestNode(track, _samplerNode);
MusicTrackSetDestNode(track2, _samplerNode2);


// Create a client
result = MIDIClientCreate(CFSTR("Virtual Client"),MyMIDINotifyProc,(__bridge void *)(self),&_virtualMidi);
NSAssert( result == noErr, @"MIDIClientCreate failed. Error code: %d '%.4s'", (int) result, (const char *)&result);


// Create an endpoint
result = MIDIDestinationCreate(_virtualMidi, (CFStringRef)@"Virtual Destination", MyMIDIReadProc, (__bridge void *)(self), &_virtualEndPoint);

NSAssert( result == noErr, @"MIDIDestinationCreate failed. Error code: %d '%.4s'", (int) result, (const char *)&result);


// ************* Set the endpoint of the sequence to be our virtual endpoint
MusicSequenceSetMIDIEndpoint(_s, _virtualEndPoint);




// Load the sequence into the music player
MusicPlayerSetSequence(_p, _s);
// Called to do some MusicPlayer setup. This just
// reduces latency when MusicPlayerStart is called
MusicPlayerPreroll(_p);
// Starts the music playing
MusicPlayerStart(_p);

还有我的 readProc 函数

void MyMIDIReadProc(const MIDIPacketList *pktlist,
                AudioProcessor *refCon,
                void *connRefCon) 


AudioUnit *player = nil;

MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
NSString *messageType;

for (int i=0; i < pktlist->numPackets; i++) 


    Byte midiStatus = packet->data[0];
    Byte midiCommand = midiStatus >> 4;// mask off all but top 4 bits
    Byte note = packet->data[1] & 0x7F;
    Byte velocity = packet->data[2] & 0x7F;

    // find the channel by masking off all but the low 4 bits
    NSInteger midiChannel = midiStatus & 0x0F;


    switch (midiStatus & 0xF0) 
        case 0x80:
            messageType = @"Note Off";
            break;

        case 0x90:
            messageType = @"Note On";
            break;

        case 0xA0:
            messageType = @"Aftertouch";
            break;

        case 0xB0:
            messageType = @"Control change";
            break;

        case 0xC0:
            messageType = @"Program Change";
            break;

        case 0xD0:
            messageType = @"Channel Pressure";
            break;

        case 0xE0:
            messageType = @"Pitch Wheel";
            break;

        default:
            messageType = @"Unk";
            break;
    
    NSLog(@"%@",messageType);
    int noteNumber = ((int) note) % 12;
    NSString *noteType;
    switch (noteNumber) 
        case 0:
            noteType = @"C";
            break;
        case 1:
            noteType = @"C#/Db";
            break;
        case 2:
            noteType = @"D";
            break;
        case 3:
            noteType = @"D#/Eb";
            break;
        case 4:
            noteType = @"E";
            break;
        case 5:
            noteType = @"F";
            break;
        case 6:
            noteType = @"F#/Gb";
            break;
        case 7:
            noteType = @"G";
            break;
        case 8:
            noteType = @"G#/Ab";
            break;
        case 9:
            noteType = @"A";
            break;
        case 10:
            noteType = @"A#/Bb";
            break;
        case 11:
            noteType = @"B";
            break;
        default:
            break;
    




    if( velocity == 0 )

        UInt32 noteOff =    kMIDIMessage_NoteOff << 4 | 0;
        if( midiChannel == 0 )
            MusicDeviceMIDIEvent (refCon.samplerUnit, noteOff, note, 0, 0);
        else if( midiChannel == 1 )
            MusicDeviceMIDIEvent (refCon.samplerUnit2, noteOff, note, 0, 0);
        

    else

        if( midiChannel == 0 )
            MusicDeviceMIDIEvent (refCon.samplerUnit, midiStatus, note, velocity, 0);
        else if( midiChannel == 1 )
            MusicDeviceMIDIEvent (refCon.samplerUnit2, midiStatus, note, velocity, 0);
        

    

    packet = MIDIPacketNext(packet);

使用我的 readProc 函数,我可以看到所有的 midi 消息,但看不到标记...

如果我在 Logic 中重新打开 midi 文件,标记在文件中,但在哪里...我如何在代码中获取它?

【问题讨论】:

请添加一些相关代码 【参考方案1】:

标记作为标记元事件包含在 MIDI 文件中。所以你需要扩展你的解析来支持元事件。元事件的状态为 0xFF。对于标记,下一个字节是 0x06,然后是长度和字符数。

【讨论】:

谢谢蒙特勒。我不明白如何“扩展”我的解析......我在 packet->data[0] 中获取事件类型,但我只找到通道事件。使用sonicspot.com/guide/midifiles.html,我了解数据包的组成,但我无法找到元数据...您能帮帮我吗? 通过我的解析,我应该有 data[0] 中的事件类型和 data[1] 中的值(注意、控制器类型、元类型),不是吗? 我搜索了很多次解决方案,但我被卡住了:(使用 MIDIReadProc 获取元事件是不可能的,只发送 Note On/Off、程序更改等。如果我添加 kMusicSequenceLoadSMF_ChannelsToTracks MusicSequenceFileLoad 的参数,我可以看到 3 个带有 CAShow(_s) 的轨道。一个速度轨道,一个带有元事件,一个带有音符。你知道如何用另一种方法拦截元事件吗?【参考方案2】:

您可以使用MusicSequenceSetUserCallback“音乐序列为添加到该序列拥有的任何音乐轨道的每个用户事件调用您的回调。”话虽如此,如果 Logic 实际上将标记导出为用户事件:

    // in your principal method after loading the sequence into the 
    result = MusicSequenceSetUserCallback(sequence, sequenceUserCallback, (__bridge void *)self);

然后将此方法添加到您的文件中:

void sequenceUserCallback (
                                  void                      *inClientData,
                                  MusicSequence             inSequence,
                                  MusicTrack                inTrack,
                                  MusicTimeStamp            inEventTime,
                                  const MusicEventUserData  *inEventData,
                                  MusicTimeStamp            inStartSliceBeat,
                                  MusicTimeStamp            inEndSliceBeat
                                  )

    // cast <yourclass>* selfPlayer = (__bridge <yourclass> *)inClientData;
    NSLog(@"track received marker");
;

如果 Logic 不将标记导出为用户事件,您可以像这样添加自己的用户事件:

 // after loading the sequence and getting the track from sequence
 static MusicEventUserData userData = 1, 0x01 ;
 result = MusicTrackNewUserEvent(track, sequenceLength /* timestamp where to invoke the callback*/ , &userData);

【讨论】:

以上是关于获取使用 Logic 生成的 midi 序列 (MusicSequence) 标记的主要内容,如果未能解决你的问题,请参考以下文章

如何获取对非默认 MIDI 音序器的引用?

Java 从 MIDI 序列中获取乐器列表

如何从 MIDI 序列中获取 Note On/Off 消息?

将 Midi 序列写入文件

获取 MIDI 设备的序列号或其他唯一 ID?

如何将 magenta.js 音符序列转换为 midi 文件?