我需要在iOS中从原始UInt16值中组装14位 MIDI Pitch Bend 值。我想知道是否有人有机会提出一个优雅的解决方案?这就是我所处的位置 - 我有机会在今天晚些时候对此进行测试,但如果我在此之前回复,那很好:
首先,一些MIDI预赛对任何人都很好奇。
MIDI Pitch Bend分为一个状态字节,后跟两个数据字节(它是一个14位控制器),这两个数据字节与其状态字节相关联,两者都以零状态< strong>位,MIDI规格让它们以MSB的顺序出现 - &gt; LSB
(编辑:更新,实际上状态 - &gt; LSB - &gt; MSB )
(即1110 0000,0111 1111,0111 1111)
面临的挑战是如何在iOS上将ARM / Intel 16位UInt16分解为两个7位段,并让它对MIDI有意义吗?
请记住,因为我们处理的是无符号整数,0值不是中性弯音,而是完全俯仰 - 中性弯音定义为8192 - 而16,383是全音高。
所以我最好猜测如何做到这一点:
UInt16 msbAnd = base10ValueUInt16 & 16256; //clearing out LSB
UInt16 msbAndShift = msbAnd << 1; //shift into leading Byte, with 0 status bit
UInt16 lsbAnd = base10ValueUInt16 & 127; //isolating LSB
UInt16 finalTwoBytePitchWord = msbFinalAndShift | lsbAnd; //make UInt16 word
UInt16 finalTwoBytePitchWordFlipped = CFSwapInt16HostToBig(finalTwoBytePitchWord); //Endian tweak
此代码运行良好,似乎创建了两个数据字节,其中包含所需的零状态位,并从小端的Intel / ARM中翻转它们,这似乎是MIDI所必需的(MIDI是STATUS - &gt; MSB - &gt; LSB ):我可以稍后使用适当的MIDI通道拍打前导状态字节。
那么,这有意义吗?有没有人想出更优雅的解决方案? (有一个我正在忽略的图书馆吗?)...我稍后会再次查看,也让大家知道这是否真的对我必须定位的采样器起作用。
由于
答案 0 :(得分:4)
我认为您的代码接近正确,但它过于复杂。这个问题与iOS或endianness或ARM或Intel无关;它只是普通的旧C比特笨拙。如果您正确编写代码,它将在any reasonable platform上运行而无需修改。你不需要图书馆;它只有几行代码。
最好在逐字节的基础上使用MIDI。您需要一个函数,它采用16位无符号整数(我们相信它最多有14位值)并返回两个单字节值,一个具有最高有效位,一个具有最低有效位。
稍后,当您发送消息时,您将按适当的顺序组合字节。 According to the specification,音高轮消息是三个字节:STATUS,然后是LSB,然后是MSB。你的问题让他们倒退了!
最不重要的7位很简单:只需从原始值中屏蔽掉这些位。最重要的7位是相似的:屏蔽掉原始值的下一个高7位,然后将它们向下移动。
16位整数在机器内存中是小端还是大端是无关紧要的;编译器负责处理。
这是一个功能和一个测试工具。
#include <stdio.h>
#include <stdint.h> // for C standard uint8_t and uint16_t
// or, if you prefer, use unsigned char and unsigned short, or Byte and UInt16;
// they'll all work, although some are more portable than others
void encode14BitValue(uint16_t value, uint8_t *out_msb, uint8_t *out_lsb)
{
uint16_t mask = 0x007F; // low 7 bits on
// "(1 << 7) - 1" is arguably clearer
*out_lsb = value & mask;
*out_msb = (value & (mask << 7)) >> 7;
}
int main(int argc, const char * argv[])
{
typedef struct {
uint16_t in;
uint8_t expected_msb;
uint8_t expected_lsb;
} test_case;
test_case cases[] = {
{ 0x0000, 0x00, 0x00 },
{ 0x0001, 0x00, 0x01 },
{ 0x0002, 0x00, 0x02 },
{ 0x0004, 0x00, 0x04 },
{ 0x0008, 0x00, 0x08 },
{ 0x0009, 0x00, 0x09 },
{ 0x000F, 0x00, 0x0F },
{ 0x0010, 0x00, 0x10 },
{ 0x0011, 0x00, 0x11 },
{ 0x001F, 0x00, 0x1F },
{ 0x0020, 0x00, 0x20 },
{ 0x0040, 0x00, 0x40 },
{ 0x0070, 0x00, 0x70 },
{ 0x007F, 0x00, 0x7F },
{ 0x0080, 0x01, 0x00 },
{ 0x0081, 0x01, 0x01 },
{ 0x008F, 0x01, 0x0F },
{ 0x0090, 0x01, 0x10 },
{ 0x00FF, 0x01, 0x7F },
{ 0x0100, 0x02, 0x00 },
{ 0x0200, 0x04, 0x00 },
{ 0x0400, 0x08, 0x00 },
{ 0x0800, 0x10, 0x00 },
{ 0x1000, 0x20, 0x00 },
{ 0x1FFF, 0x3F, 0x7F },
{ 0x2000, 0x40, 0x00 },
{ 0x2001, 0x40, 0x01 },
{ 0x3FFF, 0x7F, 0x7F },
};
int passed = 1;
for (int i = 0, c = sizeof(cases) / sizeof(cases[0]); i < c; i++) {
uint8_t msb, lsb;
encode14BitValue(cases[i].in, &msb, &lsb);
if (cases[i].expected_msb != msb || cases[i].expected_lsb != lsb) {
printf("failed: 0x%04hX expected 0x%02hhX 0x%02hhX got 0x%02hhX 0x%02hhX\n", cases[i].in, cases[i].expected_msb, cases[i].expected_lsb, msb, lsb);
passed = 0;
}
}
return passed ? 0 : 1;
}
在你的代码中,尝试将两个字节的结果打包成一个16位整数只会增加混乱。我不知道你为什么这样做,因为无论何时你在其他任何地方发送MIDI,你都必须再次提取单个字节。因为您的打包和拆包代码必须同意,所以对于字节顺序的任何担忧都会出现。你可能不会打扰。我敢打赌你的代码不正确,但你在交换MSB和LSB时的错误补偿了它。