2011-08-25 48 views
9

Tôi đang tìm một số thông tin về cách phát tệp midi trên iOS. Tôi không cần bất kỳ midi trong hoặc ngoài tin nhắn. Tôi chỉ muốn đọc tập tin midi và phát lại bản nhạc cho người dùng thay thế từng ghi chú cho mẫu âm thanh piano. Việc có thể điều chỉnh tiến độ sẽ là một yêu cầu khác.Đọc các tệp Midi trên IOS

Xin lưu ý rằng tôi không quan tâm đến việc chuyển đổi tệp midi sang định dạng wav hoặc định dạng khác. Tôi muốn đọc trực tiếp tập tin midi.

Ai có thể chỉ cho tôi theo hướng một số thông tin có thể giúp tôi hiểu quy trình được yêu cầu.

Cheers

+0

Bạn không muốn có bất kỳ tin nhắn midi nào, và bạn cũng không muốn chuyển đổi midi sang âm thanh lấy mẫu? Bạn muốn làm gì? –

+0

Anh ấy (hoặc cô ấy, nhưng hãy đối mặt với nó, có thể anh ấy) muốn sử dụng tin nhắn MIDI để điều khiển bộ tổng hợp phần mềm tích hợp trong iOS. Anh ấy hỏi liệu một thứ như vậy có tồn tại không. – SSteve

+0

Dường như iOS 4 có tính năng phát lại MIDI tích hợp. Dưới đây là một số tùy chọn của bên thứ ba: http://stackoverflow.com/questions/4240391 – SSteve

Trả lời

4

Nó dường như không có bất kỳ khuôn khổ lớn ngoài kia để hỗ trợ trong việc đọc các file MIDI, vì vậy đặt cược tốt nhất của bạn là để cuộn của riêng bạn. Dưới đây là một số tài nguyên để giúp bạn bắt đầu:

+0

Các liên kết hấp dẫn để giữ midi. Cảm ơn. – Ravi

13

tôi cũng cần chức năng này. Đây là mã cho trình phân tích cú pháp khung phân tích dữ liệu tệp MIDI được cung cấp trong đối tượng NSData (ví dụ: từ NSData: dataWithContentsOfFile) và ghi những gì nó tìm thấy vào nhật ký chuỗi có thể thay đổi. Một ứng dụng thực sự sẽ xử lý các sự kiện khác nhau một cách hữu ích hơn, nhưng đây sẽ là điểm khởi đầu tốt cho bất kỳ ai cần phân tích các tệp MIDI chuẩn vì nó xử lý hầu hết các điểm đau.

 // MidiParser.h 

     #import <Foundation/Foundation.h> 

     typedef enum tagMidiTimeFormat 
     { 
      MidiTimeFormatTicksPerBeat, 
      MidiTimeFormatFramesPerSecond 
     } MidiTimeFormat; 

     @interface MidiParser : NSObject 
     { 
      NSMutableString *log; 
      NSData *data; 
      NSUInteger offset; 

      UInt16 format; 
      UInt16 trackCount; 
      MidiTimeFormat timeFormat; 

      UInt16 ticksPerBeat; 
      UInt16 framesPerSecond; 
      UInt16 ticksPerFrame; 
     } 

     @property (nonatomic, retain) NSMutableString *log; 

     @property (readonly) UInt16 format; 
     @property (readonly) UInt16 trackCount; 
     @property (readonly) MidiTimeFormat timeFormat; 

     - (BOOL) parseData: (NSData *) midiData; 

     @end 

    // MidiParser.m 

#import "MidiParser.h" 

#define kFileCorrupt @"File is corrupt" 
#define kInvalidHeader @"Invalid MIDI header" 
#define kInvalidTrackHeader @"Invalid Track header" 

#define MAIN_HEADER_SIZE 6 

#define META_SEQUENCE_NUMBER 0x0 
#define META_TEXT_EVENT   0x1 
#define META_COPYRIGHT_NOTICE 0x2 
#define META_TRACK_NAME   0x3 
#define META_INSTRUMENT_NAME 0x4 
#define META_LYRICS    0x5 
#define META_MARKER    0x6 
#define META_CUE_POINT   0x7 
#define META_CHANNEL_PREFIX  0x20 
#define META_END_OF_TRACK  0x2f 
#define META_SET_TEMPO   0x51 
#define META_SMPTE_OFFSET  0x54 
#define META_TIME_SIGNATURE  0x58 
#define META_KEY_SIGNATURE  0x59 
#define META_SEQ_SPECIFIC  0x7f 

#define CHANNEL_NOTE_OFF  0x8 
#define CHANNEL_NOTE_ON   0x9 
#define CHANNEL_NOTE_AFTERTOUCH 0xA 
#define CHANNEL_CONTROLLER  0xB 
#define CHANNEL_PROGRAM_CHANGE 0xC 
#define CHANNEL_AFTERTOUCH  0xD 
#define CHANNEL_PITCH_BEND  0xE 

#define MICRO_PER_MINUTE  60000000 

@implementation MidiParser 

@synthesize log; 

@synthesize format; 
@synthesize trackCount; 
@synthesize timeFormat; 

- (void) dealloc 
{ 
    [log release]; 
    log = nil; 

    [super dealloc]; 
} 

- (UInt32) readDWord 
{ 
    UInt32 value = 0; 
    [data getBytes:&value range:NSMakeRange(offset, sizeof(value))]; 
    value = CFSwapInt32BigToHost(value); 
    offset += sizeof(value); 
    return value; 
} 

- (UInt16) readWord 
{ 
    UInt16 value = 0; 
    [data getBytes:&value range:NSMakeRange(offset, sizeof(value))]; 
    value = CFSwapInt16BigToHost(value); 
    offset += sizeof(value); 
    return value; 
} 

- (UInt8) readByte 
{ 
    UInt8 value = 0; 
    [data getBytes:&value range:NSMakeRange(offset, sizeof(value))]; 
    offset += sizeof(value); 
    return value; 
} 

- (UInt8) readByteAtRelativeOffset: (UInt32) o 
{ 
    UInt8 value = 0; 
    [data getBytes:&value range:NSMakeRange(offset + o, sizeof(value))]; 
    return value; 
} 

- (UInt32) readVariableValue 
{ 
    UInt32 value = 0; 

    UInt8 byte; 
    UInt8 shift = 0; 
    do 
    { 
     value <<= shift; 
     [data getBytes:&byte range:NSMakeRange(offset, 1)]; 
     offset++; 
     value |= (byte & 0x7f); 
     shift = 7; 
    } while ((byte & 0x80) != 0); 

    return value; 
} 

- (NSString *) readString: (int) length 
{ 
    char *buffer = malloc(length + 1); 
    memcpy(buffer, ([data bytes] + offset), length); 
    buffer[length] = 0x0; 
    NSString *string = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding]; 
    free(buffer); 
    return string; 
} 

- (void) readMetaSequence 
{ 
    UInt32 sequenceNumber = 0; 
    sequenceNumber |= [self readByteAtRelativeOffset:0]; 
    sequenceNumber <<= 8; 
    sequenceNumber |= [self readByteAtRelativeOffset:1]; 
    [self.log appendFormat:@"Meta Sequence Number: %d\n", sequenceNumber]; 
} 

- (void) readMetaTextEvent: (UInt32) length 
{ 
    NSString *text = [self readString:length]; 
    [self.log appendFormat:@"Meta Text: %@\n", text]; 
} 

- (void) readMetaCopyrightNotice: (UInt32) length 
{ 
    NSString *text = [self readString:length]; 
    [self.log appendFormat:@"Meta Copyright: %@\n", text]; 
} 

- (void) readMetaTrackName: (UInt32) length 
{ 
    NSString *text = [self readString:length]; 
    [self.log appendFormat:@"Meta Track Name: %@\n", text]; 
} 

- (void) readMetaInstrumentName: (UInt32) length 
{ 
    NSString *text = [self readString:length]; 
    [self.log appendFormat:@"Meta Instrument Name: %@\n", text]; 
} 

- (void) readMetaLyrics: (UInt32) length 
{ 
    NSString *text = [self readString:length]; 
    [self.log appendFormat:@"Meta Text: %@\n", text]; 
} 

- (void) readMetaMarker: (UInt32) length 
{ 
    NSString *text = [self readString:length]; 
    [self.log appendFormat:@"Meta Marker: %@\n", text];  
} 

- (void) readMetaCuePoint: (UInt32) length 
{ 
    NSString *text = [self readString:length]; 
    [self.log appendFormat:@"Meta Cue Point: %@\n", text];  
} 

- (void) readMetaChannelPrefix 
{ 
    UInt8 channel = [self readByteAtRelativeOffset:0]; 
    [self.log appendFormat:@"Meta Channel Prefix: %d\n", channel]; 
} 

- (void) readMetaEndOfTrack 
{ 
    [self.log appendFormat:@"Meta End of Track\n"]; 
} 

- (void) readMetaSetTempo 
{ 
    UInt32 microPerQuarter = 0; 
    microPerQuarter |= [self readByteAtRelativeOffset:0]; 
    microPerQuarter <<= 8; 
    microPerQuarter |= [self readByteAtRelativeOffset:1]; 
    microPerQuarter <<= 8; 
    microPerQuarter |= [self readByteAtRelativeOffset:2]; 

    UInt32 bpm = MICRO_PER_MINUTE/microPerQuarter; 
    [self.log appendFormat:@"Meta Set Tempo: Micro Per Quarter: %d, Beats Per Minute: %d\n", microPerQuarter, bpm]; 
} 

- (void) readMetaSMPTEOffset 
{ 
    UInt8 byte = [self readByteAtRelativeOffset:0]; 
    UInt8 hour = byte & 0x1f; 
    UInt8 rate = (byte & 0x60) >> 5; 
    UInt8 fps = 0; 
    switch(rate) 
    { 
     case 0: fps = 24; break; 
     case 1: fps = 25; break; 
     case 2: fps = 29; break; 
     case 3: fps = 30; break; 
     default: fps = 0; break; 
    } 
    UInt8 minutes = [self readByteAtRelativeOffset:1]; 
    UInt8 seconds = [self readByteAtRelativeOffset:2]; 
    UInt8 frame = [self readByteAtRelativeOffset:3]; 
    UInt8 subframe = [self readByteAtRelativeOffset:4]; 
    [self.log appendFormat:@"Meta SMPTE Offset (%d): %2d:%2d:%2d:%2d:%2d\n", fps, hour, minutes, seconds, frame, subframe]; 
} 

- (void) readMetaTimeSignature 
{ 
    UInt8 numerator = [self readByteAtRelativeOffset:0]; 
    UInt8 denominator = [self readByteAtRelativeOffset:1]; 
    UInt8 metro = [self readByteAtRelativeOffset:2]; 
    UInt8 thirty_seconds = [self readByteAtRelativeOffset:3]; 

    [self.log appendFormat:@"Meta Time Signature: %d/%.0f, Metronome: %d, 32nds: %d\n", numerator, powf(2, denominator), metro, thirty_seconds]; 
} 

- (void) readMetaKeySignature 
{ 
    UInt8 value = [self readByteAtRelativeOffset:0]; 
    UInt8 accidentals = value & 0x7f; 
    BOOL sharps = YES; 
    NSString *accidentalsType = nil; 
    if((value & 0x80) != 0) 
    { 
     accidentalsType = [NSString stringWithString:@"Flats"]; 
     sharps = NO; 
    } 
    else 
    { 
     accidentalsType = [NSString stringWithString:@"Sharps"]; 
    } 
    UInt8 scale = [self readByteAtRelativeOffset:1]; 
    NSString *scaleType = nil; 
    if(scale == 0) 
    { 
     scaleType = [NSString stringWithString:@"Major"]; 
    } 
    else 
    { 
     scaleType = [NSString stringWithString:@"Minor"]; 
    } 
    [self.log appendFormat:@"Meta Key Signature: %d %@ Type: %@\n", accidentals, accidentalsType, scaleType]; 
} 

- (void) readMetaSeqSpecific: (UInt32) length 
{ 
    [self.log appendFormat:@"Meta Event Sequencer Specific: - Length: %d\n", length]; 
} 

- (void) readNoteOff: (UInt8) channel parameter1: (UInt8) p1 parameter2: (UInt8) p2 
{ 
    [self.log appendFormat:@"Note Off (Channel %d): %d, Velocity: %d\n", channel, p1, p2]; 
} 

- (void) readNoteOn: (UInt8) channel parameter1: (UInt8) p1 parameter2: (UInt8) p2 
{ 
    [self.log appendFormat:@"Note On (Channel %d): %d, Velocity: %d\n", channel, p1, p2]; 
} 

- (void) readNoteAftertouch: (UInt8) channel parameter1: (UInt8) p1 parameter2: (UInt8) p2 
{ 
    [self.log appendFormat:@"Note Aftertouch (Channel %d): %d, Amount: %d\n", channel, p1, p2]; 
} 

- (void) readControllerEvent: (UInt8) channel parameter1: (UInt8) p1 parameter2: (UInt8) p2 
{ 
    [self.log appendFormat:@"Controller (Channel %d): %d, Value: %d\n", channel, p1, p2]; 
} 

- (void) readProgramChange: (UInt8) channel parameter1: (UInt8) p1 
{ 
    [self.log appendFormat:@"Program Change (Channel %d): %d\n", channel, p1]; 
} 

- (void) readChannelAftertouch: (UInt8) channel parameter1: (UInt8) p1 
{ 
    [self.log appendFormat:@"Channel Aftertouch (Channel %d): %d\n", channel, p1]; 
} 

- (void) readPitchBend: (UInt8) channel parameter1: (UInt8) p1 parameter2: (UInt8) p2 
{ 
    UInt32 value = p1; 
    value <<= 8; 
    value |= p2; 
    [self.log appendFormat:@"Pitch Bend (Channel %d): %d\n", channel, value]; 
} 

- (BOOL) parseData:(NSData *)midiData 
{ 
    BOOL success = YES; 
    self.log = [[[NSMutableString alloc] init] autorelease]; 

    @try 
    { 
     // Parse data 
     data = midiData; 
     offset = 0; 

     // If size is less than header size, then abort 
     NSUInteger dataLength = [data length]; 
     if((offset + MAIN_HEADER_SIZE) > dataLength) 
     { 
      NSException *ex = [NSException exceptionWithName:kFileCorrupt 
                 reason:kFileCorrupt userInfo:nil]; 
      @throw ex; 
     } 

     // Parse header 
     if(memcmp([data bytes], "MThd", 4) != 0) 
     { 
      NSException *ex = [NSException exceptionWithName:kFileCorrupt 
                 reason:kInvalidHeader userInfo:nil]; 
      @throw ex; 
     } 
     offset += 4; 

     UInt32 chunkSize = [self readDWord]; 
     [self.log appendFormat:@"Header Chunk Size: %d\n", chunkSize]; 

     // Read format 
     format = [self readWord]; 
     [self.log appendFormat:@"Format: %d\n", format]; 

     // Read track count 
     trackCount = [self readWord]; 
     [self.log appendFormat:@"Tracks: %d\n", trackCount]; 

     // Read time format 
     UInt16 timeDivision = [self readWord]; 
     if((timeDivision & 0x8000) == 0) 
     { 
      timeFormat = MidiTimeFormatTicksPerBeat; 
      ticksPerBeat = timeDivision & 0x7fff; 
      [self.log appendFormat:@"Time Format: %d Ticks Per Beat\n", ticksPerBeat]; 
     } 
     else 
     { 
      timeFormat = MidiTimeFormatFramesPerSecond; 
      framesPerSecond = (timeDivision & 0x7f00) >> 8; 
      ticksPerFrame = (timeDivision & 0xff); 
      [self.log appendFormat:@"Time Division: %d Frames Per Second, %d Ticks Per Frame\n", framesPerSecond, ticksPerFrame]; 
     } 

     // Try to parse tracks 
     UInt32 expectedTrackOffset = offset; 
     for(UInt16 track = 0; track < trackCount; track++) 
     { 
      if(offset != expectedTrackOffset) 
      { 
       [self.log appendFormat:@"Track Offset Incorrect for Track %d - Offset: %d, Expected: %d", track, offset, expectedTrackOffset]; 
       offset = expectedTrackOffset; 
      } 

      // Parse track header 
      if(memcmp([data bytes] + offset, "MTrk", 4) != 0) 
      { 
       NSException *ex = [NSException exceptionWithName:kFileCorrupt 
                  reason:kInvalidTrackHeader userInfo:nil]; 
       @throw ex; 
      } 
      offset += 4; 

      UInt32 trackSize = [self readDWord]; 
      expectedTrackOffset = offset + trackSize; 
      [self.log appendFormat:@"Track %d : %d bytes\n", track, trackSize]; 

      UInt32 trackEnd = offset + trackSize; 
      UInt32 deltaTime; 
      UInt8 nextByte = 0; 
      UInt8 peekByte = 0; 
      while(offset < trackEnd) 
      { 
       deltaTime = [self readVariableValue]; 
       [self.log appendFormat:@" (%05d): ", deltaTime]; 

       // Peak at next byte 
       peekByte = [self readByteAtRelativeOffset:0]; 

       // If high bit not set, then assume running status 
       if((peekByte & 0x80) != 0) 
       { 
        nextByte = [self readByte]; 
       } 

       // Meta event 
       if(nextByte == 0xFF) 
       { 
        UInt8 metaEventType = [self readByte]; 
        UInt32 metaEventLength = [self readVariableValue]; 
        switch (metaEventType) 
        { 
         case META_SEQUENCE_NUMBER: 
          [self readMetaSequence]; 
          break; 

         case META_TEXT_EVENT: 
          [self readMetaTextEvent: metaEventLength]; 
          break; 

         case META_COPYRIGHT_NOTICE: 
          [self readMetaCopyrightNotice: metaEventLength]; 
          break; 

         case META_TRACK_NAME: 
          [self readMetaTrackName: metaEventLength]; 
          break; 

         case META_INSTRUMENT_NAME: 
          [self readMetaInstrumentName: metaEventLength]; 
          break; 

         case META_LYRICS: 
          [self readMetaLyrics: metaEventLength]; 
          break; 

         case META_MARKER: 
          [self readMetaMarker: metaEventLength]; 
          break; 

         case META_CUE_POINT: 
          [self readMetaCuePoint: metaEventLength]; 
          break; 

         case META_CHANNEL_PREFIX: 
          [self readMetaChannelPrefix]; 
          break; 

         case META_END_OF_TRACK: 
          [self readMetaEndOfTrack]; 
          break; 

         case META_SET_TEMPO: 
          [self readMetaSetTempo]; 
          break; 

         case META_SMPTE_OFFSET: 
          [self readMetaSMPTEOffset]; 
          break; 

         case META_TIME_SIGNATURE: 
          [self readMetaTimeSignature]; 
          break; 

         case META_KEY_SIGNATURE: 
          [self readMetaKeySignature]; 
          break; 

         case META_SEQ_SPECIFIC: 
          [self readMetaSeqSpecific: metaEventLength]; 
          break; 

         default: 
          [self.log appendFormat:@"Meta Event Type: 0x%x, Length: %d\n", metaEventType, metaEventLength]; 
          break; 
        } 

        offset += metaEventLength; 
       } 
       else if(nextByte == 0xf0) 
       { 
        // SysEx event 
        UInt32 sysExDataLength = [self readVariableValue]; 
        [self.log appendFormat:@"SysEx Event - Length: %d\n", sysExDataLength]; 
        offset += sysExDataLength; 
       } 
       else 
       { 
        // Channel event 
        UInt8 eventType = (nextByte & 0xF0) >> 4; 
        UInt8 channel = (nextByte & 0xF); 
        UInt8 p1 = 0; 
        UInt8 p2 = 0; 

        switch (eventType) 
        { 
         case CHANNEL_NOTE_OFF: 
          p1 = [self readByte]; 
          p2 = [self readByte]; 
          [self readNoteOff: channel parameter1: p1 parameter2: p2]; 
          break; 

         case CHANNEL_NOTE_ON: 
          p1 = [self readByte]; 
          p2 = [self readByte]; 
          [self readNoteOn:channel parameter1:p1 parameter2:p2]; 
          break; 

         case CHANNEL_NOTE_AFTERTOUCH: 
          p1 = [self readByte]; 
          p2 = [self readByte]; 
          [self readNoteAftertouch:channel parameter1:p1 parameter2:p2]; 
          break; 

         case CHANNEL_CONTROLLER: 
          p1 = [self readByte]; 
          p2 = [self readByte]; 
          [self readControllerEvent:channel parameter1:p1 parameter2:p2]; 
          break; 

         case CHANNEL_PROGRAM_CHANGE: 
          p1 = [self readByte]; 
          [self readProgramChange:channel parameter1:p1]; 
          break; 

         case CHANNEL_AFTERTOUCH: 
          p1 = [self readByte]; 
          [self readChannelAftertouch:channel parameter1:p1]; 
          break; 

         case CHANNEL_PITCH_BEND: 
          p1 = [self readByte]; 
          p2 = [self readByte]; 
          [self readPitchBend:channel parameter1:p1 parameter2:p2]; 
          break; 

         default: 
          break; 
        } 

       } 
      } 
     } 

    } 
    @catch (NSException *exception) 
    { 
     success = NO; 
     [self.log appendString:[exception reason]]; 
    } 

    return success; 
} 

@end 
+1

Điều này thật tuyệt! Bạn nên tạo một khuôn khổ cho nó và đặt nó trên github. :) –

+0

@Michael McCloskey Tôi đang cố sử dụng mã của bạn làm điểm khởi đầu cho một dự án tôi đang chuyển và ba lỗi xuất hiện khi tôi đưa nó vào XCode. [đăng xuất]; [super dealloc]; self.log = [[[NSMutableString alloc] init] autorelease]; // (2nd [) Tôi có nên tắt chế độ đếm tham chiếu tự động không? –

+0

Mã đó là trước ARC. Bạn có thể chuyển đổi một tập tin đó sang chế độ ARC thông qua tùy chọn menu Edit \ Refactor \ Convert to Objectivce C ARC .... Tôi chưa chuyển đổi nội dung của mình sang ARC. –

4

Chỉ cần đọc tệp MIDI thành MusicSequence.

Mã này là từ ví dụ về PlaySequence trong Apple Documents. http://developer.apple.com/library/mac/#samplecode/PlaySequence/Listings/main_cpp.html

Xem MusicPlayer và MusicTrack quá.

OSStatus LoadSMF(const char *filename, MusicSequence& sequence, MusicSequenceLoadFlags loadFlags) 
{ 
    OSStatus result = noErr; 
    CFURLRef url = NULL; 

    ca_require_noerr (result = NewMusicSequence(&sequence), home); 

    url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)filename, strlen(filename), false); 

    ca_require_noerr (result = MusicSequenceFileLoad (sequence, url, 0, loadFlags), home); 

home: 
    if (url) CFRelease(url); 
    return result; 
} 
+0

Liên kết của bạn dành cho phát triển Mac, nhưng OP đang tìm kiếm iOS – imnk

+1

Vì iOS 5 hoạt động trên iPhone và iPad –

-1

Tôi biết đây là cuộc hội thoại cũ nhưng vẫn xuất hiện trong tìm kiếm. Trình phân tích cú pháp tệp MIDI tôi đã viết như một phần của my MIDI utilities package sẽ hoạt động trên nhiều nền tảng bất kỳ với trình biên dịch C, bao gồm cả iOS.

+0

tất cả các tệp trong zip này đều bị khóa khi yêu cầu mã hóa ... – patrick

Các vấn đề liên quan