Search code examples
iosobjective-ccnsdatansvalue

NSData vs NSValue ? how to encapsulate a C-structure in sendMIDISysExEvent:(NSData *)midiData


I'm trying work out the way to use sendMIDISysExEvent:(NSData *)midiData as a way to retune a handful of MIDI notes in a music app. I'd like the ViewController to send a MIDI message to the Synth class to change the tuning of 10 notes. The standard MIDI System Exclusive message format for retuning one note looks like this

                 F0 7F <device ID> 08 02 tt ll [kk xx yy zz] F7.

legend: tt ll [kk xx yy zz] (NB -slightly different to MIDI 1.0 Detailed Specification 4.2, p.49)

With typedef struct it would be possible to use a single float instead of a uint8_t to represent tuning frequency, e.g.

PlayViewController.h

    typedef struct
    {
    uint8_t SYSEX_SysexHeader;                // oxF0h;     // System Exclusive Header
    uint8_t SYSEX_UniversalRealTimeHeader;    // ox7Fh;     // Universal RealTime Header
    uint8_t SYSEX_myPhone;                    // ox00h;     // ID of target device (e.g. iPhone)
    uint8_t SYSEX_subID1;                     // ox08h;     // sub-ID #1 (MIDI Tuning Standard)
    uint8_t SYSEX_subID2;                     // ox02h;     // sub-ID #2 (note change)
    uint8_t SYSEX_tuningProgramNumber;        // ox0h;      // tuning program number (0 -127)
    uint8_t SYSEX_numberOfKeys;               // ox10h;     // number of changes

    uint8_t SYSEX_key0;
    float   TUNING_pitch_0;

    uint8_t SYSEX_key1;
    float   TUNING_pitch_1;

    uint8_t SYSEX_key2;
    float   TUNING_pitch_2;

    uint8_t SYSEX_key3;
    float   TUNING_pitch_3;

    uint8_t SYSEX_key4;
    float   TUNING_pitch_4;

    uint8_t SYSEX_key5;
    float   TUNING_pitch_5;

    uint8_t SYSEX_key6;
    float   TUNING_pitch_6;

    uint8_t SYSEX_key7;
    float   TUNING_pitch_7;

    uint8_t SYSEX_key8;
    float   TUNING_pitch_8;

    uint8_t SYSEX_key9;
    float   TUNING_pitch_9;

    uint8_t eox;                                // OxF7h;       //

    }
    TuneEvent;

and still in .h

    typedef NS_ENUM(NSInteger, SYSEX)
    {
    SYSEX_SysexHeader               = 240,
    SYSEX_UniversalRealTimeHeader   = 127,
    SYSEX_myPhone                   = 0,
    SYSEX_subID1                    = 8,
    SYSEX_subID2                    = 2,
    SYSEX_tuningProgramNumber       = 0,
    SYSEX_numberOfKeysToBeChanged   = 1,
    SYSEX_key0                      = 61,
    SYSEX_key1                      = 62,
    SYSEX_key2                      = 63,
    SYSEX_key3                      = 64,
    SYSEX_key4                      = 65,
    SYSEX_key5                      = 66,
    SYSEX_key6                      = 67,
    SYSEX_key7                      = 68,
    SYSEX_key8                      = 69,
    SYSEX_key9                      = 70,
    SYSEX_eox                       = 247
    };

    typedef NS_ENUM(NSInteger, TUNING)
    {
    TUNING_pitch0,
    TUNING_pitch1,
    TUNING_pitch2,
    TUNING_pitch3,
    TUNING_pitch4,
    TUNING_pitch5,
    TUNING_pitch6,
    TUNING_pitch7,
    TUNING_pitch8,
    TUNING_pitch9
    };

    float TUNING_float(TUNING micro);

and finally for the floating point values ... (thanks to this answer)

PlayViewController.m

    float TUNING_float(TUNING micro) 
    {
    switch (micro) 
        {
        case TUNING_pitch0:
            return  579.4618f;
        case TUNING_pitch1:
            return  607.0552f;
        case TUNING_pitch2:
            return  662.2421f;
        case TUNING_pitch3:
            return  708.2311f;
        case TUNING_pitch4:
            return  772.6157f;
        case TUNING_pitch5:
            return  809.4070f;
        case TUNING_pitch6:
            return  882.9894f;
        case TUNING_pitch7:
            return  910.5828f;
        case TUNING_pitch8:
            return  993.3631f;
        case TUNING_pitch9:
            return  1030.1540f;
        default:
            return   0.0f;
        }
    }

However when I read this answer I began to ask how I might go about preparing a MIDI data packet based on NSData that sendMIDISysExEvent:(NSData *)midiData will send. And this answer recommends NSValue as the better way to encapsulate a C-structure like the one I'm trying to create so I'm confused. If that really is the case, I'm puzzled why Apple would introduce a method like sendMIDISysExEvent:(NSData *)midiData.

My question is: based on the message format (outlined in my code above), how would I prepare midiData in order to send it using this method ?

CONCLUSION

In response to the accepted answer, length of "raw binary data", defined in terms of number of bytes rather than type of data, explains the salient difference between NSData and NSValue. This would also suggest sendMIDISysExEvent:(NSData *)midiData was only designed to handle bytes of data i.e. uint8_t and not float. In other words, the sensible option is to express frequency values using bytes as per the following extract from the MIDI Tuning Standard

    yy = MSB of fractional part (1/128 semitone = 100/128 cents = .78125 cent units)
    zz = LSB of fractional part (1/16384 semitone = 100/16384 cents = .0061 cent units)

Solution

  • Both NSData and NSValue are wrappers around "raw binary data", yet with a different intent and with a different way in defining the length of the binary data.

    NSValue targets in boxing scalar types like int, float (but also a struct), and the interface of NSValue is designed to pass the length of the "data" by means of the type. Hence, it is not possible to encapsulate objects of variable length (like VLA or C-strings) within an NSValue, since the size of the data cannot be derived from the type information (cf. NSValue):

    The type you specify must be of constant length. You cannot store C strings, variable-length arrays and structures, and other data types of indeterminate length in an NSValue—you should use NSString or NSData objects for these types.

    NSData is meant as a box for arbitrary byte buffers. So it has an interface through which you can define the length of the "raw binary data" in terms of bytes (and not in terms of a type).

    Concerning your question, as the MIDI-interface in sendMIDISysExEvent:(NSData *)midiData expects an NSData-object, the only (meaningful) way is to pass your object as an NSData-object.

    Hope it helps.