Songforthedeaf
Songforthedeaf

Reputation: 21

Get midi sequence (MusicSequence) markers generates with Logic

I'm developing an app which play a midi sequence (.mid) with audio units. The midi file is created with Logic which offers the possibility to add markers on the timeline.

In the code, I use MusicSequence MusicPlayer to read the file, and MIDIClientCreate MIDIDestinationCreate to parse MIDI packets.

The principal method

    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);

And my readProc function

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);
}

}

With my readProc function, I can see all midi messages but not the markers...

If I reopen the midi file in Logic, the markers are in file, but where... How can I get it in the code ?

Upvotes: 2

Views: 1449

Answers (2)

mahal tertin
mahal tertin

Reputation: 3416

You could use MusicSequenceSetUserCallback "The music sequence invokes your callback for each user event added to any music track owned by the sequence." That being said if Logic actually exports the markers as user-events:

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

Then add this method to your file:

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");
};

If Logic doesn't export the marker as user event, you could add your own user-events like this:

 // 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);

Upvotes: 0

John Holcroft
John Holcroft

Reputation: 473

Markers are contained in the MIDI file as Marker Meta Events. So you need to extend your parsing to support meta events. Meta Events have the status 0xFF. For markers the next byte is 0x06, then a length and a number of characters.

Upvotes: 1

Related Questions