Play MIDI files with multiple iOS instruments

Good evening. I am trying to write simple midi players with a high-quality sound bank. Faced with the problem of playing midi files. The problem is that all the midi tracks (drums, pads, bass, synthesizer, etc.) were played, but they play the same instrument. I found a solution for OS X bu I need a solution for iOS. Should I create for each Instrument audioUnit with kAudioUnitSubType_Sampler?

Tell me, is it possible to change the instrument on the selected channel in real time? How can this be implemented? Sorry for my English ((

Here is my code, it does not work correctly:

// 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; } 
+6
source share
1 answer

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]]; } } } 
+3
source

All Articles