Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play multi-instrument MIDI file IOS

I'm trying to write a simple midi player with a quality sound bank but I'm faced with the problem of playing midi files. The problem is that all midi tracks (drums, pads, bass, synth, etc.) played, but they play a single instrument. I found a solution for OS X but I need a solution for iOS.

Shall I have to create audioUnit with kAudioUnitSubType_Sampler for each instrument?

Or is it possible to change the instrument on the selected channel in real time? How it can be implemented?

Here is my code, it does not work properly:

// Create a client
MIDIClientRef virtualMidi;
Check(MIDIClientCreate(CFSTR("Virtual Client"),
                       MyMIDINotifyProc,
                       NULL,
                       &virtualMidi));


// Create an endpoint
MIDIEndpointRef virtualEndpoint;
Check(MIDIDestinationCreate(virtualMidi, CFSTR("Virtual Destination"), MyMIDIReadProc, samplerUnit, &virtualEndpoint));

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

if (!midiFilePath) {
    midiFilePath = [[NSBundle mainBundle]
                    pathForResource:@"carelesswhisper"
                    ofType:@"mid"];

}
NSLog(@"midiFilePath %@", midiFilePath);

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

MidiParser *midiParser = [[MidiParser alloc] init];
NSData *data = [NSData dataWithContentsOfFile:midiFilePath];
[midiParser parseData:data];
NSString *midiInfo = [midiParser log];
NSLog(@"midiInfo %@", midiInfo);

MusicSequenceLoadFlags  loadFlags = 0;
loadFlags = kMusicSequenceLoadSMF_ChannelsToTracks;
MusicSequenceFileLoad(midiSequence, (__bridge CFURLRef) midiFileURL, 0, loadFlags);

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

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

if (!soundBankFilePath) {
    soundBankFilePath = [[NSBundle mainBundle] pathForResource:@"SGM-V2.01-1" ofType:@"sf2"];
}
NSLog(@"soundBankFilePath %@", soundBankFilePath);

NSURL *presetURL = [NSURL fileURLWithPath:soundBankFilePath];

// Initialise the sound font
AUSamplerInstrumentData bpdata;
bpdata.fileURL  = (__bridge CFURLRef) presetURL;
bpdata.bankMSB  = kAUSampler_DefaultMelodicBankMSB;
bpdata.bankLSB  = kAUSampler_DefaultBankLSB;
bpdata.instrumentType = kInstrumentType_SF2Preset;

// set the kAUSamplerProperty_LoadPresetFromBank property
result = AudioUnitSetProperty(samplerUnit,
                              kAUSamplerProperty_LoadInstrument,
                              kAudioUnitScope_Global,
                              0,
                              &bpdata,
                              sizeof(bpdata));
MusicPlayerSetSequence(midiPlayer, midiSequence);
// Called to do some MusicPlayer setup. This just
// reduces latency when MusicPlayerStart is called
//    MusicPlayerPreroll(midiPlayer);
// Starts the music playing
MusicPlayerStart(midiPlayer);

// Get length of track so that we know how long to kill time for
MusicTrack track;
MusicTimeStamp len;
UInt32 sz = sizeof(MusicTimeStamp);
MusicSequenceGetIndTrack(midiSequence, 1, &track);
MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &len, &sz);


while (1) { // kill time until the music is over
    usleep (3 * 1000 * 1000);
    MusicTimeStamp now = 0;
    MusicPlayerGetTime (midiPlayer, &now);
    if (now >= len)
        break;
}
like image 808
John Avatar asked Apr 14 '14 17:04

John


1 Answers

I found the answer. For each track in sequence requires a separate AUSampler.

EDITED: 1 Oct 2016. I'm sorry for the long answer.

There is my code for play midi file.

 - (void)loadMidi:(NSString*)midiFilePath andSoundBank:(NSString*)soundBankFilePath {

[self setupStereoStreamFormat];
[self createGraph];
Check(AUGraphInitialize (graph));
Check(AUGraphStart (graph));

// get URL midi file
if (!midiFilePath) {
    midiFilePath = [[NSBundle mainBundle] pathForResource:@"Ресницы" ofType:@"kar"];
}
NSLog(@"midiFilePath %@", midiFilePath);
NSURL * midiFileURL = [NSURL fileURLWithPath:midiFilePath];

// get URL SFbank
if (!soundBankFilePath) {
    soundBankFilePath = [[NSBundle mainBundle] pathForResource:@"SGM-V2.01-1" ofType:@"sf2"];
}
NSLog(@"soundBankFilePath %@", soundBankFilePath);
bankUrl = [NSURL fileURLWithPath:soundBankFilePath];

// create sequence from midi
sequence = 0;

Check(NewMusicSequence(&sequence));
Check(MusicSequenceFileLoad (sequence, (__bridge CFURLRef)(midiFileURL), 0, 0));

MidiParser *parser = [[MidiParser alloc] init];
[parser parseData:[NSData dataWithContentsOfFile:midiFilePath]];
NSLog(@"PARSE MIDI %@", [parser log]);
metaLyrics = [[NSMutableArray alloc] initWithArray:[parser syllableArray]];

// do not delete set sequense to graph
Check(MusicSequenceSetAUGraph(sequence, graph));

[self getMidiNodesArray];

// read each track and set instruments & effects & volume for AUSamplers
[self parseSequence];

CAShow(sequence);
MusicTrack tempoTrack;
MusicSequenceGetTempoTrack(sequence, &tempoTrack);
NSDictionary *infoDict = (__bridge NSDictionary *)(MusicSequenceGetInfoDictionary(sequence));
float tempo = [[infoDict valueForKey:@"tempo"] floatValue];

CAShow(tempoTrack);

NSLog(@"Tempo in sequence %f", tempo);

// Load the sequence into the music player
Check(NewMusicPlayer (&player));
// setup speed player
Check(MusicPlayerSetPlayRateScalar(player, 1));
Check(MusicPlayerSetSequence(player, sequence));
Check(MusicPlayerSetTime(player, 0));
MusicPlayerPreroll(player);
}

So parse midi tracks code:

- (void) parseSequence {
// get numbers of tracks
UInt32 numTracks;
Check(MusicSequenceGetTrackCount(sequence, &numTracks));
NSLog(@"Number of tarcks %d", (unsigned int)numTracks);

// mute some tracks if needed
NSSet *mutedTracks = [NSSet setWithObjects: @"11", nil];

// mute unused channels
NSLog(@"LOADING TRACKS");
for (UInt32 i = 0; i < numTracks; ++i) {
    MusicTrack track;
    MusicTimeStamp trackLength;
    UInt32 propsize = sizeof(MusicTimeStamp);
    Check(MusicSequenceGetIndTrack(sequence, i, &track));
    Check(MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength,
                                &trackLength, &propsize));
    // log track info if needed
    CAShow(track);
    MusicEventIterator myIterator;
    MusicTimeStamp timeStamp;
    MusicEventType eventType;
    const void *refData = 0;
    UInt32 dataSize;

    Check(NewMusicEventIterator(track, &myIterator));
    Boolean hasCurrentEvent;
    Check(MusicEventIteratorHasCurrentEvent (myIterator, &hasCurrentEvent));
    NSMutableSet *instrumentsSet = [[NSMutableSet alloc] init];
    int noActions = 0;

    while (hasCurrentEvent) {
        MusicEventIteratorGetEventInfo(myIterator, &timeStamp, &eventType, &refData, &dataSize);


        if (eventType == 7) {
            NSData *dataChaunk = [[NSData alloc] initWithBytes:refData length:dataSize];
            void *channelByte_0 = 0;
            void *channelByte_1 = 0;
            void *channelByte_2 = 0;
            void *channelByte_3 = 0;

            [dataChaunk getBytes:&channelByte_0 range:NSMakeRange(0, 1)];
            [dataChaunk getBytes:&channelByte_1 range:NSMakeRange(1, 1)];
            [dataChaunk getBytes:&channelByte_2 range:NSMakeRange(2, 1)];
            [dataChaunk getBytes:&channelByte_3 range:NSMakeRange(3, 1)];
            Byte command = (int)channelByte_0;


            if (command < 208 && command >= 192) {
                // setup track on AUsampler
                if (![instrumentsSet containsObject:[NSString stringWithFormat:@"%d",(command & 0xf)]]) {
                    [self setDestNode:(command & 0xf) forTrack:track];
                    [self setInstrumentMSB:(int)channelByte_1 presetLSB:0 trackID:(command & 0xf)];
                }
                [instrumentsSet addObject:[NSString stringWithFormat:@"%d",(command & 0xf)]];

            } else if ( command <192 && command >= 176){
                switch ((NSInteger)channelByte_1) {
                    case 0: // bank select MSB
                        NSLog(@"CHANNEL %d CONTROLLER 0xB bankMSB value %d", command & 0xf, (int)channelByte_2);
                        break;
                    case 7: // chanell volume
                        [self setVolume:(int)channelByte_2 inChannel:(command & 0xf)];
                        break;
                    case 10: // pan
                        [self setPan:(int)channelByte_2 inChannel:(command & 0xf)];
                        break;
                    case 32: // bank select LSB
                        NSLog(@"CHANNEL %d CONTROLLER 0xB bankLSB value %d", command & 0xf, (int)channelByte_2);
                        break;
                    case 94:
                        NSLog(@"CHANNEL %d CONTROLLER 0xB setEffect value %d", command & 0xf, (int)channelByte_2);
                        break;


                    default:
                        break;
                }
            } else

                noActions++;
        }
        // do work here
        MusicEventIteratorNextEvent (myIterator);
        MusicEventIteratorHasCurrentEvent (myIterator, &hasCurrentEvent);
    }
    NSLog(@"No actions count %d", noActions);


    if ([mutedTracks count] > 0 && [mutedTracks containsObject:[NSString stringWithFormat:@"%d", (unsigned int)i]])
    {
        Boolean mute = true;
        Check(MusicTrackSetProperty(track, kSequenceTrackProperty_MuteStatus, &mute, sizeof(mute)));
        printf ("played tracks %u\n", (unsigned int)i);
    }
    instrumentsSet = nil;
}


UInt32 nodeInd = [[midiNodesArray objectAtIndex:9] intValue];
NSLog(@"setPercussionBankMSB %d for %d track %d node", 1, 9, (unsigned int)nodeInd);
AUNode node;
AudioUnit unit;
Check(AUGraphGetIndNode(graph, nodeInd, &node));
Check(AUGraphNodeInfo(graph, node, 0, &unit));


AUSamplerInstrumentData bpdata;
bpdata.fileURL  = (__bridge CFURLRef) bankUrl;
bpdata.bankMSB  = kAUSampler_DefaultPercussionBankMSB;
bpdata.bankLSB  = kAUSampler_DefaultBankLSB;
bpdata.instrumentType = kInstrumentType_SF2Preset;
bpdata.presetID = (UInt8) 1;
Check(AudioUnitSetProperty (unit,
                            kAUSamplerProperty_LoadInstrument,
                            kAudioUnitScope_Global, 0,
                            &bpdata, sizeof(bpdata)));

[self setVolume:20 inChannel:0];
}

Get midi tracks from graph:

- (void) getMidiNodesArray {
UInt32 nodeCount;
Check(AUGraphGetNodeCount (graph, &nodeCount));
AudioUnit outSynth;

if (!midiNodesArray) {
    midiNodesArray = [[NSMutableArray alloc] init];
}

for (UInt32 i = 0; i < nodeCount; ++i)
{
    AUNode node;
    Check(AUGraphGetIndNode(graph, i, &node));

    AudioComponentDescription desc;
    Check(AUGraphNodeInfo(graph, node, &desc, 0));

    if (desc.componentSubType == kAudioUnitSubType_Sampler) {
        Check(AUGraphNodeInfo(graph, node, 0, &outSynth));
        [midiNodesArray addObject:[NSString stringWithFormat:@"%d", (unsigned int)i]];
    }
}
}
like image 113
John Avatar answered Nov 15 '22 21:11

John