Hi,
I test AVMIDIPlayer in order to replace classes written based on AVAudioEngine with callbacks functions sending MIDI events
to test, I use an NSMutableData filled with:
- the MIDI header
- a track for time signature
- a track containing a few midi events.
I then create an instance of the AVMIDIPlayer using the data
Everything works fine for some instrument (00 … 20) or 90 but not for other instruments 60, 70, …
The MiDI header and the time signature track are based on the MIDI.org sample, https://midi.org/standard-midi-files-specification RP-001_v1-0_Standard_MIDI_Files_Specification_96-1-4.pdf
the midi events are:
UInt8 trkEvents[] = {
0x00, 0xC0, instrument, // Tubular bell
0x00, 0x90, 0x4C, 0xA0, // Note 4C
0x81, 0x40, 0x48, 0xB0, // TS + Note 48
0x00, 0xFF, 0x2F, 0x00}; // End
for (UInt8 i=0; i<3; i++) {
printf("0x%X ", trkEvents[i]);
}
printf("\n");
[_midiTempData appendBytes:trkEvents length:sizeof(trkEvents)];
A template application is used to change the instrument in a NSTextField
I was wondering if specifics are required for some instruments?
The interface header:
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestMIDIPlayer : NSObject
@property (retain) NSMutableData *midiTempData;
@property (retain) NSURL *midiTempURL;
@property (retain) AVMIDIPlayer *midiPlayer;
- (void)createTest:(UInt8)instrument;
@end
NS_ASSUME_NONNULL_END
The implementation:
#pragma mark -
typedef struct _MThd {
char magic[4]; // = "MThd"
UInt8 headerSize[4]; // 4 Bytes, MSB first. Always = 00 00 00 06
UInt8 format[2]; // 16 bit, MSB first. 0; 1; 2 Use 1
UInt8 trackCount[2]; // 16 bit, MSB first.
UInt8 division[2]; //
}MThd;
MThd MThdMake(void);
void MThdPrint(MThd *mthd) ;
typedef struct _MIDITrackHeader {
char magic[4]; // = "MTrk"
UInt8 trackLength[4]; // Ignore, because it is occasionally wrong.
} Track;
Track TrackMake(void);
void TrackPrint(Track *track) ;
#pragma mark - C Functions
MThd MThdMake(void) {
MThd mthd = {
"MThd",
{0, 0, 0, 6},
{0, 1},
{0, 0},
{0, 0}
};
MThdPrint(&mthd);
return mthd;
}
void MThdPrint(MThd *mthd) {
char *ptr = (char *)mthd;
for (int i=0;i<sizeof(MThd); i++, ptr++) {
printf("%X", *ptr);
}
printf("\n");
}
Track TrackMake(void) {
Track track = {
"MTrk",
{0, 0, 0, 0}
};
TrackPrint(&track);
return track;
}
void TrackPrint(Track *track) {
char *ptr = (char *)track;
for (int i=0;i<sizeof(Track); i++, ptr++) {
printf("%X", *ptr);
}
printf("\n");
}
@implementation TestMIDIPlayer
- (id)init {
self = [super init];
printf("%s %p\n", __FUNCTION__, self);
if (self) {
_midiTempData = nil;
_midiTempURL = [[NSURL alloc]initFileURLWithPath:@"midiTempUrl.mid"];
_midiPlayer = nil;
[self createTest:0x0E];
NSLog(@"_midiTempData:%@", _midiTempData);
}
return self;
}
- (void)dealloc {
[_midiTempData release];
[_midiTempURL release];
[_midiPlayer release];
[super dealloc];
}
- (void)createTest:(UInt8)instrument {
/* MIDI Header */
[_midiTempData release];
_midiTempData = nil;
_midiTempData = [[NSMutableData alloc]initWithCapacity:1024];
MThd mthd = MThdMake();
MThd *ptrMthd = &mthd;
ptrMthd->trackCount[1] = 2;
ptrMthd->division[1] = 0x60;
MThdPrint(ptrMthd);
[_midiTempData appendBytes:ptrMthd length:sizeof(MThd)];
/* Track Header
Time signature */
Track track = TrackMake();
Track *ptrTrack = &track;
ptrTrack->trackLength[3] = 0x14;
[_midiTempData appendBytes:ptrTrack length:sizeof(track)];
UInt8 trkEventsTS[]= {
0x00, 0xFF, 0x58, 0x04, 0x04, 0x04, 0x18, 0x08, // Time signature 4/4; 18; 08
0x00, 0xFF, 0x51, 0x03, 0x07, 0xA1, 0x20, // tempo 0x7A120 = 500000
0x83, 0x00, 0xFF, 0x2F, 0x00 }; // End
[_midiTempData appendBytes:trkEventsTS length:sizeof(trkEventsTS)];
/* Track Header
Track events */
ptrTrack->trackLength[3] = 0x0F;
[_midiTempData appendBytes:ptrTrack length:sizeof(track)];
UInt8 trkEvents[] = {
0x00, 0xC0, instrument, // Tubular bell
0x00, 0x90, 0x4C, 0xA0, // Note 4C
0x81, 0x40, 0x48, 0xB0, // TS + Note 48
0x00, 0xFF, 0x2F, 0x00}; // End
for (UInt8 i=0; i<3; i++) {
printf("0x%X ", trkEvents[i]);
}
printf("\n");
[_midiTempData appendBytes:trkEvents length:sizeof(trkEvents)];
[_midiTempData writeToURL:_midiTempURL atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if (!_midiPlayer.isPlaying)
[self midiPlay];
});
}
- (void)midiPlay {
NSError *error = nil;
_midiPlayer = [[AVMIDIPlayer alloc]initWithData:_midiTempData soundBankURL:nil error:&error];
if (_midiPlayer) {
[_midiPlayer prepareToPlay];
[_midiPlayer play:^{
printf("Midi Player ended\n");
[_midiPlayer stop];
[_midiPlayer release];
_midiPlayer = nil;
}];
}
}
@end
Call from AppDelegate
- (IBAction)actionInstrument:(NSTextField*)sender {
[_testMidiplayer createTest:(UInt8)sender.intValue];
}