0

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?

gerasim13
  • 13
  • 1
  • 5
  • Why aren't you using a `MIDIRawData` structure? – CL. May 07 '14 at 14:36
  • @CL. Using MIDIRawData i get the same result. Moreover i want use my own data structure. I take my struct and cast it to MIDIRawData. (found mention of this pattern at http://lists.apple.com/archives/coreaudio-api/2006/Feb/msg00147.html) – gerasim13 May 08 '14 at 11:41
  • You must wrap your data into a SysEx message, and you must wrap that message into a structure with the same layout as the `MIDIRawData` structure. Anyway, for the SysEx, what is your [manufacturer ID](http://www.midi.org/techspecs/manid.php)? – CL. May 08 '14 at 12:29
  • @CL. I dont have an manufacturer identifier. It seemed to me that sysex is a good idea for storing custom data in midi file. Can you provide me an example how to wrap structs in sysex and sysex messages into MIDIRawData? – gerasim13 May 08 '14 at 14:02
  • If you have not bought an ID, you cannot use a SysEx message. – CL. May 08 '14 at 14:17
  • @CL, I just want to store my custom data in midi file, that's all i want. Suppose that I bought id, Whats is next step? – gerasim13 May 08 '14 at 14:24
  • @CL. Thank you. Despite the fact that you were wrong, you pointed me on the right path. – gerasim13 May 08 '14 at 14:43
  • @CL. I mean that i may use any id that i want (even not registered in mma). This will work – gerasim13 May 08 '14 at 14:48
  • You must not use an unregistered ID; this can conflict with other programs or with a newly registered ID. – CL. May 08 '14 at 14:50
  • I think it is ok in my case. – gerasim13 May 08 '14 at 21:32

2 Answers2

0

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;
    }
gerasim13
  • 13
  • 1
  • 5
  • You cannot use `memccpy` because your data could contain zero bytes. The size of `raw.data` is just one byte; you have a buffer overflow and are corrupting your stack. – CL. May 08 '14 at 14:48
  • In other cases this is true (almost always), but this code is works like charm. Saved file successfully opened using MidiKit App or using my test app. – gerasim13 May 08 '14 at 21:37
  • but you are right when says about zero byte in data. i should use memcpy instead of memccpy. – gerasim13 May 08 '14 at 21:52
0

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