ios - 播放多乐器 MIDI 文件 IOS

标签 ios midi sample audio-player midi-instrument

我正在尝试编写一个具有高质量音色库的简单 midi 播放器,但我遇到了播放 midi 文件的问题。问题是所有 MIDI 音轨(鼓、打击垫、贝斯、合成器等)都在播放,但它们只演奏一种乐器。我找到了适用于 OS X 的解决方案,但我需要适用于 iOS 的解决方案。

我是否必须为每个乐器创建带有 kAudioUnitSubType_SampleraudioUnit

或者是否可以实时更改所选 channel 上的乐器?如何实现?

这是我的代码,它不能正常工作:

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

最佳答案

我找到了答案。对于序列中的每个轨道,都需要一个单独的 AUSampler。

编辑:2016 年 10 月 1 日。很抱歉回答太长。

有我的播放midi文件的代码。

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

因此解析 midi 音轨代码:

- (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];
}

从图中获取 midi 音轨:

- (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]];
    }
}
}

关于ios - 播放多乐器 MIDI 文件 IOS,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23066937/

相关文章:

ios - 在 Xcode 8 中添加开发团队?

ios - UISearchController 在编辑时添加偏移量

ios - 如何禁用 ios-charts 中的十字和其他触摸事件?

swift - AKMIDICallbackInstrument 实现问题

java - 只用 ant 运行 NetBeans 项目

安卓样本错误

ios - 以编程方式更改 UICollectionView 属性

javascript - 使用 MIDI.js 播放 MIDI 文件时如何调整速度?

javascript - 使用 javascript 连接 midi 键盘或其他实时 midi 输入?