【问题标题】:Reading Midi files on IOS在 IOS 上读取 Midi 文件
【发布时间】:2011-08-25 16:10:20
【问题描述】:

我正在寻找有关如何在 IOS 上播放 midi 文件的一些信息。我不需要任何 midi 输入或输出消息。我只是想读取 MIDI 文件并将曲目播放回用户,用每个音符替换钢琴声音样本。能够调整节奏将是另一个要求。

请注意,我对将 midi 文件转换为 wav 或其他格式不感兴趣。我想直接阅读midi文件。

谁能指出一些信息的方向,这可能有助于我理解所需的过程。

干杯

【问题讨论】:

  • 您不想要任何 MIDI 输出消息,也不想将 MIDI 转换为采样声音?你想做什么
  • 他(或者她,但是,让我们面对现实吧,可能是他)想要使用 MIDI 消息来控制 iOS 中的内置软件合成器。他在问这样的事情是否存在。
  • iOS 4 好像没有内置 MIDI 播放功能。这里有几个第 3 方选项:stackoverflow.com/questions/4240391

标签: ios midi


【解决方案1】:

我也需要这个功能。这是一个骨架解析器的代码,它解析 NSData 对象中提供的 MIDI 文件数据(例如,来自 NSData:dataWithContentsOfFile)并将它找到的内容写入可变字符串日志。一个真正的应用程序会以更有用的方式处理各种事件,但这对于需要解析标准 MIDI 文件的任何人来说应该是一个很好的起点,因为它处理了大部分的痛点。

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

【讨论】:

  • 这太棒了!您应该为它制作一个框架并将其放在 github 上。 :)
  • @Michael McCloskey 我正在尝试使用您的代码作为我正在移植的项目的起点,当我将它带入 XCode 时出现了三个错误。 [日志发布]; [超级释放]; self.log = [[[NSMutableString alloc] init] autorelease]; //(2nd [ ) 我应该退出自动引用计数模式吗?
  • 该代码是 ARC 之前的代码。您可以通过 Edit\Refactor\Convert to Objectivce C ARC... 菜单选项将该文件转换为 ARC 模式。我还没有将我的东西转换为 ARC。
  • 哇!感谢您的代码。您介意解释一下- (UInt32) readVariableValue 的作用吗?里面有很多位运算。
  • 在对 Dan the Man 的一个非常延迟的回复中,该方法正在处理 MIDI 数据的结构方式,以便在每个字节的低七位中包含有意义的数据,第八位是一个标志有点像。老实说,我不记得是我写的还是从另一种语言移植的,但我认为这就是那里发生的事情。
【解决方案2】:

似乎没有任何主要的框架可以帮助读取 MIDI 文件,所以最好的办法是自己动手。以下是一些帮助您入门的资源:

【讨论】:

  • 获取midi的好链接。谢谢。
【解决方案3】:

只需将 MIDI 文件读入 MusicSequence。

此代码来自 Apple Docs 中的 PlaySequence 示例。 http://developer.apple.com/library/mac/#samplecode/PlaySequence/Listings/main_cpp.html

看看 MusicPlayer 和 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;
}

【讨论】:

  • 您的链接是用于 Mac 开发的,但 OP 正在寻找 iOS
  • 从 iOS 5 开始,现在也可以在 iPhone 和 iPad 上使用
【解决方案4】:

自从有人问这个问题以来已经有一段时间了,所以这些天有一些相关的框架。例如,一种方法是使用AudioKit。它的部分基于 Apple 现有的 MIDI 内容以及一些正在开发的自定义类。

【讨论】:

    【解决方案5】:

    我知道这是一个古老的对话,但它仍然出现在搜索中。我作为my MIDI utilities package 的一部分编写的 MIDI 文件解析器应该可以在几乎任何带有 C 编译器的平台上运行,包括 iOS。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多