How to add sysex data to MusicTrack? (AudioToolbox)

477 Views Asked by At

I'm trying to write my little midi sequencer with blackjack and etc but stuck on writing sysex data into MusicTrack. I use following code to insert sysex events

// ---- Some code here --- //

PatternData pattern = { sizeof(PatternData), i, signature.numerator, signature.denominator };
CABarBeatTime beattime = CABarBeatTime((i * signature.numerator * signature.denominator) + 1, 1, 0, SUBBEAT_DIVISOR_DEFAULT);
// Convert beattime to timestamp 
if ((MusicSequenceBarBeatTimeToBeats(sequence, &beattime, &timestamp)) != noErr) 
{
return status; 
} 
// Add event 
if ((status = MusicTrackNewMIDIRawDataEvent(track, timestamp, (MIDIRawData*)&pattern)) != noErr) 
{ 
return status; 
}

// ---- Some code here --- //

PatternData is

typedef struct PatternData 
{ 
UInt32 length; // Struct length 
UInt8 index; // Pattern index 
UInt8 bars; // Number of bars in patten 
UInt8 beats; // Number of beats in pattern 
} PatternData;

I did something wrong because after call MusicSequenceFileCreate i get corrupted file. Does somebody have an example of how to add sysex data to a music track?

2

There are 2 best solutions below

3
gerasim13 On

Ok. I found a right way, here is it:

    UInt8 data[] = { 0xF0, manufacturerId, databyte1, databyte2, databyte3, 0xF7 };
    MIDIRawData raw;
    memcpy(raw.data, data, 0, sizeof(data));
    raw.length = sizeof(data);

    if ((status = MusicTrackNewMIDIRawDataEvent(track, timestamp, &raw)) != noErr)
    {
        return status;
    }
0
Walter Schurter On

Here is an example, how to record normal MIDI and SYSEX messages in a MIDI track and save them in a MIDI file in the shared iTunes folder (set "Application supports iTunes file sharing" to "YES" in .plist): See specially "calloc" in the code!!

#import "ViewController.h"
#import <CoreMIDI/MIDIServices.h>
#import <CoreMIDI/CoreMIDI.h>
#import "AppDelegate.h"
#include <sys/time.h>
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@end

@implementation ViewController

@synthesize SYSEX_8;

long secTempA = 0;
float secTempB = 0;
long secStartA = 0;
float secStartB = 0;
MusicTimeStamp timeStamp = 0;
MusicSequence recordSequence;
MusicTrack recordTrack;
MusicTimeStamp lenRec = 0;
MIDINoteMessage noteMessage;
MusicTrack track;
NSString *fileNameForSave = @"";
NSString *midiFileWritePath = @"";
NSString *documentsDirectoryPath = @"";
UIAlertView *infoStore;
UIAlertView *infoStoreError;
MIDIRawData *sysexData;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // Get documents Directory
    // (don't forget the ".plist" entry "Application supports iTunes file sharing YES"
    NSArray *pathDocDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    documentsDirectoryPath = [pathDocDir objectAtIndex:0];

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)SYSEX_8_touchdown:(id)sender { 
    NewMusicSequence(&recordSequence);
    MusicSequenceNewTrack(recordSequence, &recordTrack);
    MusicSequenceSetSequenceType(recordSequence, kMusicSequenceType_Beats);

    timeStamp = 0;
    struct timeval time;
    gettimeofday(&time, NULL);
    secStartA = time.tv_sec;
    secStartB = time.tv_usec * 0.000001;

    noteMessage.channel = 0x90; // Note ON 
    noteMessage.note = 0x3C;
    noteMessage.velocity = 0x7F;
    MusicTrackNewMIDINoteEvent(recordTrack, timeStamp, &noteMessage);
    NSLog(@"%02x %02x %02x", 0x90, 0x3C, 0x7F);

    usleep(10000);

    gettimeofday(&time, NULL);
    secTempA = time.tv_sec;
    secTempB = time.tv_usec * 0.000001;
    secTempA = secTempA - secStartA;
    secTempB = secTempB - secStartB;
    timeStamp = (secTempA + secTempB) * 2;

    noteMessage.channel = 0x90; // Note OFF
    noteMessage.note = 0x3C;
    noteMessage.velocity = 0x00;
    MusicTrackNewMIDINoteEvent(recordTrack, timeStamp, &noteMessage);
    NSLog(@"%02x %02x %02x", 0x90, 0x3C, 0x00);

    usleep(100000);

    gettimeofday(&time, NULL);
    secTempA = time.tv_sec;
    secTempB = time.tv_usec * 0.000001;
    secTempA = secTempA - secStartA;
    secTempB = secTempB - secStartB;
    timeStamp = (secTempA + secTempB) * 2;

    Byte datatest[8];
    UInt32 theSize = offsetof(MIDIRawData, data[0]) + (sizeof(UInt8) * sizeof(datatest));
    sysexData = (MIDIRawData *)calloc(1, theSize);
    sysexData->length = sizeof(datatest);

    datatest[0] = 0xF0;  // Start SYSEX
    datatest[1] = 0x26;
    datatest[2] = 0x79;
    datatest[3] = 0x0E;
    datatest[4] = 0x00;
    datatest[5] = 0x00;
    datatest[6] = 0x00;
    datatest[7] = 0xF7;  // End SYSEX

    for (int j = 0; j < sizeof(datatest); j++) {
        sysexData->data[j] = datatest[j];
        NSLog(@"%02x", sysexData->data[j]);
    }

    int status;
    if ((status = MusicTrackNewMIDIRawDataEvent(recordTrack, timeStamp, sysexData) != noErr)) {
        NSLog(@"error %i", status);
    }
    else {
        [self stopRecording];
    }
}

- (void) stopRecording {

    CAShow(recordSequence);  // To show all MIDI events !!!

    UInt32 sz = sizeof(MusicTimeStamp);
    lenRec = 0;
    MusicSequenceGetIndTrack(recordSequence, 0, &track);
    MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &lenRec, &sz);

    if (lenRec > 0.1){
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss +zzzz"];
        NSDate *startDate = [NSDate date];

        NSTimeZone *zone = [NSTimeZone systemTimeZone];
        NSInteger interval = [zone secondsFromGMTForDate:startDate];
        startDate = [startDate dateByAddingTimeInterval:interval];
        //    NSLog(@"Date: %@", startDate);

        NSString *strDate = [[NSString alloc] initWithFormat:@"%@", startDate];
        NSArray *arr = [strDate componentsSeparatedByString:@" "];
        NSString *str;
        str = [arr objectAtIndex:0];
        NSArray *arr_date = [str componentsSeparatedByString:@"-"];

        int year = [[arr_date objectAtIndex:0] intValue];
        int month = [[arr_date objectAtIndex:1] intValue];
        int day = [[arr_date objectAtIndex:2] intValue];

        str = [arr objectAtIndex:1];
        NSArray *arr_time = [str componentsSeparatedByString:@":"];

        int hours = [[arr_time objectAtIndex:0] intValue];
        int minutes = [[arr_time objectAtIndex:1] intValue];
        int seconds = [[arr_time objectAtIndex:2] intValue];

        fileNameForSave = [NSString stringWithFormat:@"%@_%04d%02d%02d_%02d%02d%02d%@", @"$Record", year, month, day, hours, minutes, seconds, @".mid"];
        midiFileWritePath = [documentsDirectoryPath stringByAppendingPathComponent:fileNameForSave];

        infoStore = [[UIAlertView alloc]initWithTitle: @"Save as MIDI file ?"
                                              message: [NSString stringWithFormat:@"\n%@", fileNameForSave]
                                             delegate: self
                                    cancelButtonTitle: @"YES"
                                    otherButtonTitles: @"NO",nil];
        [infoStore show];  // rest siehe unten !!!!!
    }
    else {
        MusicSequenceDisposeTrack(recordSequence, track);
        DisposeMusicSequence(recordSequence);
    }

}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(int)buttonIndex {
    // deletion code here
    if (alertView == infoStore) {
        if (buttonIndex == 0) {
            NSURL *midiURL = [NSURL fileURLWithPath:midiFileWritePath];
            OSStatus status = 0;
            status = MusicSequenceFileCreate(recordSequence, (__bridge CFURLRef)(midiURL), kMusicSequenceFile_MIDIType, kMusicSequenceFileFlags_EraseFile, 0);
            if (status != noErr) {
                infoStoreError = [[UIAlertView alloc]initWithTitle: @"Information"
                                                           message: [NSString stringWithFormat:@"\nError storing MIDI file in: %@", documentsDirectoryPath]
                                                          delegate: self
                                                 cancelButtonTitle: nil
                                                 otherButtonTitles:@"OK",nil];
                [infoStoreError show];
            }
        }
        MusicSequenceDisposeTrack(recordSequence, track);
        DisposeMusicSequence(recordSequence);
    }

}

@end