如何将 sysex 数据添加到 MusicTrack? (音频工具箱)

Posted

技术标签:

【中文标题】如何将 sysex 数据添加到 MusicTrack? (音频工具箱)【英文标题】:How to add sysex data to MusicTrack? (AudioToolbox) 【发布时间】:2014-05-07 13:58:08 【问题描述】:

我正在尝试用二十一点等编写我的小型 midi 音序器,但坚持将 sysex 数据写入 MusicTrack。我使用以下代码插入 sysex 事件

// ---- 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 是

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;

我做错了什么,因为在调用 MusicSequenceFileCreate 后我得到了损坏的文件。 有人有如何将 sysex 数据添加到音乐曲目的示例吗?

【问题讨论】:

你为什么不使用MIDIRawData 结构? @CL。使用 MIDIRawData 我得到了相同的结果。此外,我想使用我自己的数据结构。我将我的结构转换为 MIDIRawData。 (在lists.apple.com/archives/coreaudio-api/2006/Feb/msg00147.html 发现了这种模式的提及) 您必须将数据包装到 SysEx 消息中,并且必须将该消息包装到与 MIDIRawData 结构具有相同布局的结构中。无论如何,对于 SysEx,你的 manufacturer ID 是什么? @CL。我没有制造商标识符。在我看来,sysex 是在 midi 文件中存储自定义数据的好主意。你能给我一个例子,如何将 sysex 和 sysex 消息中的结构包装到 MIDIRawData 中吗? 如果您没有购买ID,则无法使用SysEx消息。 【参考方案1】:

好的。我找到了正确的方法,就是这样:

    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;
    

【讨论】:

您不能使用memccpy,因为您的数据可能包含零字节。 raw.data 的大小只有一个字节;你有一个缓冲区溢出并且正在破坏你的堆栈。 在其他情况下这是真的(几乎总是),但这段代码就像魅力一样。使用 MidiKit 应用程序或使用我的测试应用程序成功打开保存的文件。 但是当你说数据中的零字节时你是对的。我应该使用 memcpy 而不是 memccpy。【参考方案2】:

这里是一个例子,如何在一个 MIDI 轨道中录制正常的 MIDI 和 SYSEX 消息,并将它们保存在共享 iTunes 文件夹中的一个 MIDI 文件中(在 .plist 中将“应用程序支持 iTunes 文件共享”设置为“YES”): 见代码中的“calloc”!

#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

【讨论】:

以上是关于如何将 sysex 数据添加到 MusicTrack? (音频工具箱)的主要内容,如果未能解决你的问题,请参考以下文章

如何从音量级别创建 MIDI Sysex Master Volume 消息?

使用 Arduino 发送 MIDI SysEx 消息?

Alesis QS MIDI Sysex 数据转换

MIDI 蓝牙 LE、SYSEX 消息不完整

发送和接收 Java Midi Sysex 消息

SysEx 不会发送字节“AD”