Вычисление значений Pitch Bend CoreMIDI для iOS?
Мне нужно вручную собрать 14-битные значения Pitch Bend MIDI из необработанных значений UInt16 в iOS. Мне интересно, был ли у кого-нибудь шанс придумать элегантное решение? Вот где я нахожусь - у меня будет шанс проверить это, вероятно, позже сегодня, но если я услышу назад до того, здорово:
Во-первых, некоторые предварительные MIDI-записи для любого любопытного.
MIDI Pitch Bend разбит на один байт состояния, за которым следуют два байта данных (это 14-битный контроллер), эти два байта данных связаны с их байтом состояния, оба ведущие с нулевым битом состояния, в спецификации MIDI они появляются в порядке MSB -> LSB
(Изменить: Обновить, это на самом деле Статус -> LSB -> MSB)
(т.е. 1110 0000, 0111 1111, 0111 1111)
Задача состоит в том, как разбить ARM/Intel 16bit UInt16 на два 7-битных сегмента на iOS, и имеет ли это смысл для 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 -> MSB -> LSB): я могу шлепнуть позже на ведущий байт состояния с соответствующим MIDI-каналом.
Итак, имеет ли это смысл? Кто-нибудь придумал более элегантное решение? (есть ли библиотека, которую я пропускаю?) ... Я проверю позже, а также сообщу людям, действительно ли это работает на семплере, на который я должен нацелиться.
Спасибо
1 ответ
Я думаю, что ваш код близок к правильному, но он слишком сложен. Этот вопрос не имеет ничего общего с 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 компенсировала это.