4

I have a MIDI Synth Unit

 AudioComponentDescription midiSynthDesc;
  midiSynthDesc.componentType = kAudioUnitType_MusicDevice;
  midiSynthDesc.componentSubType = kAudioUnitSubType_MIDISynth;
  midiSynthDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
  midiSynthDesc.componentFlags = 0;
  midiSynthDesc.componentFlagsMask = 0;

which used to be in an AUGraph. But since AUGraph is deprecated, I used AudioComponentInstanceNew to create it without using AUNode and AUGraph

AudioComponent foundMIDISynthReference = AudioComponentFindNext ( NULL, &midiSynthDesc);
  AudioComponentInstanceNew(foundMIDISynthReference, &midiSynthUnit);

I was using it to play Sequence by attaching the Sequence to AUGraph

NSString *presetURLPath = [[NSBundle mainBundle] pathForResource:@"GortsMiniPianoJ1" ofType:@"SF2"];
NSURL * presetURL = [NSURL fileURLWithPath:presetURLPath]; 
[self loadFromDLSOrSoundFont: (NSURL *)presetURL withPatch: (int)3];

NSString *midiFilePath = [[NSBundle mainBundle] pathForResource:name ofType:@"mid"];
NSURL * midiFileURL = [NSURL fileURLWithPath:midiFilePath];

NewMusicPlayer(&musicPlayer);
MusicPlayerSetSequence(musicPlayer, musicSequence);
MusicSequenceSetAUGraph(musicSequence, _processingGraph);
MusicPlayerPreroll(musicPlayer);
MusicPlayerStart(musicPlayer);

But now that AUGraph is deprecated, using AudioUnit only, how can I use Play MIDI Files in Core Audio?

John
  • 2,672
  • 2
  • 23
  • 29
  • You can't use AVAudioSequencer or AVMIDIPlayer, I assume? – matt Jul 20 '20 at 16:22
  • @matt I tend to use AudioUnit for I have other audio processing work that needed AudioUnits. It would be nice if I could put everything together in AudioUnit. Also I have little experience with AVAudioSequencer or AVMIDIPlayer, I need to load SoundFont too for playing. So AudioUnit to play MIDI is "nice have" but not a "must have". Do you recommend any resources for me to learn more about AVAudioSequencer or AVMIDIPlayer? Thanks – John Jul 20 '20 at 16:37
  • Well, the point about AUGraph being deprecated is that it is replaced by AVAudioEngine. Apple has given many WWDC talks over the years explaining AVAudioEngine and preparing for this. I suggest you watch them and see if you can take the changes on board. – matt Jul 20 '20 at 16:50
  • @John I'm curious if you made any progress on this? I'm finding myself in a similar situation with a need to update some complicated legacy MusicSequence and MusicPlayer code and my attempts at using these APIs with AVAudioEngine in place of AUGraph have yet to be successful. From what I can see, AVMIDIPlayer and AVAudioSequencer still do not support manipulation of sequences and tracks to the level that the MusicSequence APIs do. Thanks in advance! – Derek Lee Sep 26 '20 at 12:34

1 Answers1

1

MusicSequence accepts a reference to a MIDIEndPointRef instead of an AUGraph. A possible workaround would be to create an internal virtual port to receive the events sent by the MusicPlayer and redispatch them to your audio unit(s). Here is a simplified example :

Creates a MIDI client :

MIDIClientRef clientRef;
    id myManager;
    MIDIClientCreate(CFSTR("MyMidiClient"), NULL, (__bridge void *)myManager, &clientRef);

Creates a virtual input port :

MIDIEndpointRef endPointRef;
MIDIDestinationCreate(clientRef, CFSTR("MyVirtualInPort"), (MIDIReadProc)MidiInputCallback, (__bridge void *)myManager, &endPointRef);

MIDI events sent by the MusicPlayer are received through the callback referenced when creating the port :

void MidiInputCallback(const MIDIPacketList *pktlist, void *refCon, void *connRefCon)
{
    id myManager = (__bridge id)refCon;
    MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
    UInt8 status, byte1, byte2;
    for (unsigned int ipack = 0; ipack < pktlist->numPackets; ++ipack)
    {
        status = packet->data[0];
        byte1 = packet->data[1];
        byte2 = packet->data[2];

        // Do something with the MIDI events
        printf("Received Event %d %d %d\n", status, byte1, byte2);

        if (packet) packet = MIDIPacketNext(packet);
    }
}

Creates a very simple MusicPlayer containing a single note :

MusicSequence musicSequence;
MusicPlayer player;
MusicTrack track;
NewMusicSequence(&musicSequence);
NewMusicPlayer(&player);
MusicPlayerSetSequence(player, musicSequence);
MusicSequenceNewTrack(musicSequence, &track);

MIDIChannelMessage msg = {
    .status = 0 | 9 << 4, // channel 1, type note on
    .data1 = 60,          // pitch
    .data2 = 80           // velocity
};
MusicTrackNewMIDIChannelEvent(track, 0.0, &msg);
msg.data2 = 0;            // pseudo note off
MusicTrackNewMIDIChannelEvent(track, 4.0, &msg);

References the EndPoint and starts playing : the played note is received by the callback where it can be redirected to an audio unit of type music device.

MusicSequenceSetMIDIEndpoint(musicSequence, endPointRef); //the port created above

MusicPlayerStart(player);
dspr
  • 2,383
  • 2
  • 15
  • 19