1

我需要从 iOS 中的原始 UInt16 值手动组装 14 位MIDI Pitch Bend值。我想知道是否有人有机会提出一个优雅的解决方案?这就是我所在的位置 - 我可能会在今天晚些时候有机会对此进行测试,但如果我在那之前收到回复,那就太好了:

首先,为好奇的人准备一些 MIDI 预备知识。

MIDI Pitch Bend 被分解为一个状态字节,然后是两个数据字节(它是一个 14 位控制器),这两个数据字节与它们的状态字节相关联,它们都以零状态开头,MIDI 规范让它们按以下顺序出现最高位 -> 最低位

(编辑:更新,它实际上是Status -> LSB -> 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

这段代码运行良好,似乎创建了两个具有所需零状态位的数据字节,并从小端英特尔/ARM 翻转它们,这似乎是 MIDI 所必需的(MIDI 是 STATUS -> MSB -> LSB ):我可以拍稍后使用适当的 MIDI 通道在前导状态字节上。

那么,这有意义吗?有没有人想出一个更优雅的解决方案?(有没有我忽略的图书馆?)......我稍后会回来检查并让人们知道这是否真的适用于我必须针对它的采样器。

谢谢

4

1 回答 1

4

我认为您的代码接近正确,但它过于复杂。这个问题与 iOS 或字节序或 ARM 或 Intel 无关;它只是简单的旧 C 位玩弄。如果您正确编写代码,它将在任何合理的平台上运行而无需修改。你不需要图书馆;这只是几行代码。

最好逐字节地使用 MIDI。您需要一个函数,它接受一个 16 位无符号整数(我们相信它最多具有 14 位值)并返回两个单字节值,一个具有最高有效位,一个具有最低有效位。

稍后,当您发送消息时,您以适当的顺序组装字节。根据规范,音高轮消息是三个字节: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 时的错误弥补了它。

于 2012-11-28T06:55:50.183 回答