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)
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.