I have found the answer. Each track in the sequence requires a separate AUSampler.
EDITED: October 1, 2016. Sorry for the long reply.
There is my code for playing a 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 the MIDI 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 the 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]]; } } }