2011-08-25 19 views
9

Szukam informacji na temat odtwarzania pliku midi na IOS. Nie potrzebuję żadnych komunikatów midi in i out. Po prostu chciałbym przeczytać plik midi i odtworzyć utwór z powrotem do użytkownika, zastępując każdą nutę próbką dźwięku fortepianu. Możliwość regulacji tempa będzie kolejnym wymaganiem.Czytanie plików Midi na IOS

Uwaga: nie interesuje mnie konwersja pliku midi do formatu WAV lub innego formatu. Chciałbym bezpośrednio przeczytać plik midi.

Czy ktokolwiek może wskazać mi w kierunku niektórych informacji, które mogą mi pomóc w zrozumieniu wymaganego procesu.

Cheers

+0

Nie chcesz żadnych komunikatów midi out, a nie chcesz konwertować midi na samplowany dźwięk? Co chcesz robić? –

+0

On (lub ona, ale spójrzmy prawdzie w oczy, prawdopodobnie on) chce używać komunikatów MIDI do kontrolowania wbudowanego syntezatora oprogramowania w iOS. Pyta, czy coś takiego istnieje. – SSteve

+0

Wygląda na to, że iOS 4 nie ma wbudowanego odtwarzania MIDI. Oto kilka opcji stron trzecich: http://stackoverflow.com/questions/4240391 – SSteve

Odpowiedz

13

ja też potrzebne tej funkcji. Oto kod dla parsera szkieletu, który analizuje dane pliku MIDI dostarczone w obiekcie NSData (np. Z NSData: dataWithContentsOfFile) i zapisuje znalezione informacje w zmiennym dzienniku łańcuchów. Prawdziwa aplikacja przetwarzałaby różne zdarzenia w bardziej użyteczny sposób, ale powinien to być dobry punkt wyjścia dla każdego, kto musi przeanalizować standardowe pliki MIDI, ponieważ zajmuje się większością punktów bólu.

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

To jest świetne! Powinieneś stworzyć dla niego framework i umieścić go na githubie. :) –

+0

@ Michael McCloskey Próbuję użyć twojego kodu jako punktu wyjścia dla projektu, który przenosiłem, i pojawiły się trzy błędy kiedy przyniosłem go do XCode. [wydanie dziennika]; [super dealloc]; self.log = [[[NSMutableString alloc] init] autorelease]; // (2nd [) Czy powinienem po prostu przełączyć się z trybu automatycznego liczenia odniesienia? –

+0

Ten kod jest przed ARC. Możesz przekonwertować ten jeden plik do trybu ARC za pomocą opcji menu Edycja \ Refaktor \ Konwertuj na ZOBACZ C ARC ... Nie zmieniłem jeszcze moich rzeczy na ARC. –

4

Po prostu odczytaj plik MIDI w MusicSequence.

Ten kod pochodzi z przykładu PlaySequence w Apple Docs. http://developer.apple.com/library/mac/#samplecode/PlaySequence/Listings/main_cpp.html

Zobacz także MusicPlayer i MusicTrack.

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

Twój odnośnik jest przeznaczony dla Maca, ale OP szuka iOS – imnk

+1

Od czasu iOS 5 działa to również na iPhone'ie i iPadzie –

-1

Wiem, że to stara rozmowa, ale wciąż pojawia się w wyszukiwaniach. Parser plików MIDI, który napisałem jako część my MIDI utilities package powinien działać na praktycznie każdej platformie z kompilatorem C, w tym iOS.

+0

wszystkie pliki w tym zip są zablokowane na żądanie chmodowania ... – patrick