2

我的目标是将来自 Alesis 合成器的字节码流转换为人类可读的格式。我需要能够获取“程序转储”并读取组成补丁名称的 10 个字符串。

为了从合成器接收“程序转储”,我通过 MIDI-OX 向合成器发送了以下命令:

F0 00 00 0E 0E 01 73 F7

我要求它向我发送程序 73 的转储。

我收到了这个:

F0 00 00 0E 0E 00 73 00 60 24 0B 27 27 01 64 1E 19 19 05 23 19 1E 2A 41 0D 23 46 19 1E 06 00 47 0D 23 30 6C 18 63 30 6C 18 40 3F 0A 67 1B 16 20 40 00 60 18 00 18 06 05 0C 2B 41 13 70 05 30 40 31 63 70 05 00 40 31 63 70 05 00 40 31 63 00 4C 2A 51 00 46 7F 78 18 40 0F 40 31 40 31 04 30 0C 00 30 6C 03 30 3C 0F 00 00 05 0A 0F 14 19 1E 23 28 2D 72 00 76 34 3C 54 42 19 46 0C 33 3C 0C 00 0E 1B 46 60 58 31 46 61 58 31 00 7F 14 4E 37 6C 74 13 00 40 31 00 30 0C 0A 18 56 02 27 60 0B 60 00 63 46 61 0B 00 00 63 46 61 0B 00 00 63 46 01 18 55 22 01 0C 7F 71 31 00 1F 00 63 00 63 08 60 18 00 60 58 07 60 18 1E 00 00 0A 14 1E 28 32 3C 46 50 5A 64 01 0C 2D 15 29 05 36 0C 19 66 78 18 00 1C 36 0C 41 31 63 0C 43 31 63 00 7E 29 1C 6F 58 00 01 02 00 63 00 60 18 14 30 2C 05 4E 40 17 40 01 46 0D 43 17 00 00 46 0D 43 17 00 00 46 0D 03 30 2A 45 02 18 7E 63 63 00 3E 00 46 01 46 11 40 31 00 40 31 0F 40 71 3D 00 00 14 28 3C 50 64 78 0C 21 35 49 03 58 4C 71 31 1C 6C 18 32 4C 71 31 00 38 6C 18 02 63 46 19 06 63 46 01 7C 53 00 60 18 53 37 6C 70 0D 03 40 31 28 60 58 0A 1C 01 2F 00 03 0C 1B 06 2F 00 00 0C 1B 06 2F 00 00 0C 1B 06 60 54 0A 05 30 7C 47 47 01 7C 00 0C 03 0C 23 00 63 00 00 63 1E 3C 63 18 00 00 28 50 78 20 49 71 19 42 6A 12 07 F7 

MIDI-OX 告诉我它收到了 408 个字节。

这符合规范:

“单个程序转储发送了 400 个数据字节,对应于 350 个字节的程序数据。加上标头,与程序转储一起传输的总字节数为 408。显示了每个参数在程序转储中的位置在下一节中。”

“程序转储”应由以下值组成:

F0 00 00 0E 0E 00 <program#> <data> F7

这意味着数据应以“00 60”开头并以“07 F7”结尾。

现在我应该能够将这 400 字节转换为该程序的“350 字节的打包参数数据”。遵循“程序数据格式”,程序名称的 10 位数字应位于打包数据中的某处。补丁 73 被称为“BlowDeTune”或“PanBristle”,不完全确定它是从 0 开始还是从 1 开始。

那么你如何进行转换呢?规范的第1页给出了传输格式,但我不明白如何解压。

任何人都可以帮忙吗?

Alesis QS MIDI Sysex 规范在这里:

http://www.midiworld.com/quadrasynth/qs_swlib/qs678r.pdf

MIDI-OX 可以在这里找到:

http://www.midiox.com/

4

2 回答 2

3

你很幸运,因为几年前我玩了一点 Midi(用我的 Atari ST 520)所以我对这个话题有足够的兴趣来调查一下......

作为记录,我根据您为合成器提供的参考找到了MIDI System Exclusive Message格式。
我最初以为在这个页面中描述了打包算法,但是在实现了它的解码和创建垃圾之后,我发现我错了......我会给出这个代码以防万一它在其他地方对你有用......

第一次尝试很有用,因为当我重新阅读 PDF 文件中的规范时,我了解到 A7 到 G0 符号实际上是位...

数据被“打包”,因为 Midi 非控制字必须是 7 位干净的(高位始终未设置)。
他们获取 7 个字节的原始数据,将其视为 56 位的大字,然后将该字每 7 位拆分一次,使高位始终为 0:

原始数据(使用不同的符号):

0 - b07 b06 b05 b04 b03 b02 b01 b00
1 - b17 b16 b15 b14 b13 b12 b11 b10
2 - b27 b26 b25 b24 b23 b22 b21 b20
3 - b37 b36 b35 b34 b33 b32 b31 b30
4 - b47 b46 b45 b44 b43 b42 b41 b40
5 - b57 b56 b55 b54 b53 b52 b51 b50
6 - b67 b66 b65 b64 b63 b62 b61 b60

传输/编码数据:

0 -  0  b06 b05 b04 b03 b02 b01 b00
1 -  0  b15 b14 b13 b12 b11 b10 b07
2 -  0  b24 b23 b22 b21 b20 b17 b16
3 -  0  b33 b32 b31 b30 b27 b26 b25
4 -  0  b42 b41 b40 b37 b36 b35 b34
5 -  0  b51 b50 b47 b46 b45 b44 b43
6 -  0  b60 b57 b56 b55 b54 b53 b52
7 -  0  b67 b66 b65 b64 b63 b62 b61

所以我们有:

0 - 00000000 0x00
1 - 01100000 0x60
2 - 00100100 0x24
3 - 00001011 0x0B
4 - 00100111 0x27
5 - 00100111 0x27
6 - 00000001 0x01
7 - 01100100 0x64

0 - 00011110 0x1E
1 - 00011001 0x19
2 - 00011001 0x19
3 - 00000101 0x05
4 - 00100011 0x23
5 - 00011001 0x19
6 - 00011110 0x1E
7 - 00101010 0x2A

一旦解码,我们应该有:

0 - 00000000 0x00
1 - 00110000 0x30
2 - 01101001 0x69
3 - 01110001 0x71
4 - 00111010 0x3A
5 - 00000101 0x05
6 - 11001000 0xC8

0 - 10011110 0x9E
1 - 01001100 0x4C
2 - 10100110 0xA6
3 - 00110000 0x30
4 - 11001010 0xCA
5 - 01111000 0x78
6 - 01010100 0x54

我相信我正确解码了数据,但仍然有垃圾(即不可读的字符串)......
也许你会在我的代码中看到一个逻辑错误,这可能是一个起点。

我看到 MIDI-OX 可以使用 WSH 编写脚本,所以我编写了一个使用 WSH 运行的 JS 脚本,并在控制台上输出:

var midiData =
[
  0xF0, 0x00, 0x00, 0x0E, 0x0E, 0x00, 0x73,
  0x00, 0x60, 0x24, 0x0B, 0x27, 0x27, 0x01, 0x64, 0x1E, 0x19, 0x19, 0x05, 0x23, 0x19, 0x1E, 0x2A,
  0x41, 0x0D, 0x23, 0x46, 0x19, 0x1E, 0x06, 0x00, 0x47, 0x0D, 0x23, 0x30, 0x6C, 0x18, 0x63, 0x30,
  0x6C, 0x18, 0x40, 0x3F, 0x0A, 0x67, 0x1B, 0x16, 0x20, 0x40, 0x00, 0x60, 0x18, 0x00, 0x18, 0x06,
  0x05, 0x0C, 0x2B, 0x41, 0x13, 0x70, 0x05, 0x30, 0x40, 0x31, 0x63, 0x70, 0x05, 0x00, 0x40, 0x31,
  0x63, 0x70, 0x05, 0x00, 0x40, 0x31, 0x63, 0x00, 0x4C, 0x2A, 0x51, 0x00, 0x46, 0x7F, 0x78, 0x18,
  0x40, 0x0F, 0x40, 0x31, 0x40, 0x31, 0x04, 0x30, 0x0C, 0x00, 0x30, 0x6C, 0x03, 0x30, 0x3C, 0x0F,
  0x00, 0x00, 0x05, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x23, 0x28, 0x2D, 0x72, 0x00, 0x76, 0x34, 0x3C,
  0x54, 0x42, 0x19, 0x46, 0x0C, 0x33, 0x3C, 0x0C, 0x00, 0x0E, 0x1B, 0x46, 0x60, 0x58, 0x31, 0x46,
  0x61, 0x58, 0x31, 0x00, 0x7F, 0x14, 0x4E, 0x37, 0x6C, 0x74, 0x13, 0x00, 0x40, 0x31, 0x00, 0x30,
  0x0C, 0x0A, 0x18, 0x56, 0x02, 0x27, 0x60, 0x0B, 0x60, 0x00, 0x63, 0x46, 0x61, 0x0B, 0x00, 0x00,
  0x63, 0x46, 0x61, 0x0B, 0x00, 0x00, 0x63, 0x46, 0x01, 0x18, 0x55, 0x22, 0x01, 0x0C, 0x7F, 0x71,
  0x31, 0x00, 0x1F, 0x00, 0x63, 0x00, 0x63, 0x08, 0x60, 0x18, 0x00, 0x60, 0x58, 0x07, 0x60, 0x18,
  0x1E, 0x00, 0x00, 0x0A, 0x14, 0x1E, 0x28, 0x32, 0x3C, 0x46, 0x50, 0x5A, 0x64, 0x01, 0x0C, 0x2D,
  0x15, 0x29, 0x05, 0x36, 0x0C, 0x19, 0x66, 0x78, 0x18, 0x00, 0x1C, 0x36, 0x0C, 0x41, 0x31, 0x63,
  0x0C, 0x43, 0x31, 0x63, 0x00, 0x7E, 0x29, 0x1C, 0x6F, 0x58, 0x00, 0x01, 0x02, 0x00, 0x63, 0x00,
  0x60, 0x18, 0x14, 0x30, 0x2C, 0x05, 0x4E, 0x40, 0x17, 0x40, 0x01, 0x46, 0x0D, 0x43, 0x17, 0x00,
  0x00, 0x46, 0x0D, 0x43, 0x17, 0x00, 0x00, 0x46, 0x0D, 0x03, 0x30, 0x2A, 0x45, 0x02, 0x18, 0x7E,
  0x63, 0x63, 0x00, 0x3E, 0x00, 0x46, 0x01, 0x46, 0x11, 0x40, 0x31, 0x00, 0x40, 0x31, 0x0F, 0x40,
  0x71, 0x3D, 0x00, 0x00, 0x14, 0x28, 0x3C, 0x50, 0x64, 0x78, 0x0C, 0x21, 0x35, 0x49, 0x03, 0x58,
  0x4C, 0x71, 0x31, 0x1C, 0x6C, 0x18, 0x32, 0x4C, 0x71, 0x31, 0x00, 0x38, 0x6C, 0x18, 0x02, 0x63,
  0x46, 0x19, 0x06, 0x63, 0x46, 0x01, 0x7C, 0x53, 0x00, 0x60, 0x18, 0x53, 0x37, 0x6C, 0x70, 0x0D,
  0x03, 0x40, 0x31, 0x28, 0x60, 0x58, 0x0A, 0x1C, 0x01, 0x2F, 0x00, 0x03, 0x0C, 0x1B, 0x06, 0x2F,
  0x00, 0x00, 0x0C, 0x1B, 0x06, 0x2F, 0x00, 0x00, 0x0C, 0x1B, 0x06, 0x60, 0x54, 0x0A, 0x05, 0x30,
  0x7C, 0x47, 0x47, 0x01, 0x7C, 0x00, 0x0C, 0x03, 0x0C, 0x23, 0x00, 0x63, 0x00, 0x00, 0x63, 0x1E,
  0x3C, 0x63, 0x18, 0x00, 0x00, 0x28, 0x50, 0x78, 0x20, 0x49, 0x71, 0x19, 0x42, 0x6A, 0x12, 0x07,
  0xF7
];

// Show original data
DumpData(midiData, 16);

var headerLength = 7; // Bytes to skip
var resultData = new Array();
var decodedByteCount = 0;  // Number of expanded bytes in result

var cumulator = 0;
var bitCount = 0;
for (var i = headerLength; // Skip header
    i < midiData.length - 1; // Omit EOF
    i++)
{
  var rank = (i - headerLength) % 8; // We split the data in runs of 8 bytes
  // We cumulate the bits of these runs (less the high bit) to make a big word of 56 bits
/*
  cumulator |= midiData[i] << (7 * rank);
  if (rank == 7)  // End of the run
  {
    // Split the cumulator in 7 bytes
    for (var j = 0; j < 7; j++)
    {
      var shift = 8 * j;
      var byte = (cumulator & (0xFF << shift)) >> shift;
      WScript.StdOut.Write(ByteToHex(byte) + ' ');
      resultData[decodedByteCount++] = byte;
    }
    cumulator = 0;  // Reset the buffer
  }
*/
  // Actually, we cannot do that, because JS' bit arithmetic seems to be limited to signed 32 bits!
  // So I get the bytes out as soon as they are complete.
  // Somehow, it is more elegant anyway (but reflects less the original algorithm).
  cumulator |= midiData[i] << bitCount;
  bitCount += 7;
//~   WScript.StdOut.Write((i - 7) + ':' + ByteToHex(midiData[i]) + ' (' + bitCount + ') ' + DecimalToHex(cumulator) + '\n');
  if (bitCount >= 8)
  {
    var byte = cumulator & 0xFF;
    bitCount -= 8;
    cumulator >>= 8;
    resultData[decodedByteCount++] = byte;
//~     WScript.StdOut.Write((i - 7) + ':' + ByteToHex(midiData[i]) + ' (' + bitCount + ') ' + DecimalToHex(cumulator) + ' > '  + ByteToHex(byte) + '\n');
  }
}
DumpData(resultData, 14);

实用程序:

function DumpData(data, lineLength)
{
  WScript.StdOut.Write("Found " + data.length + " bytes\n");
  var txt = '';
  for (var i = 0; i < data.length; i++)
  {
    var rd = data[i];
    if (rd > 31)
    {
      txt += String.fromCharCode(rd);
    }
    else
    {
      txt += '.';
    }
    WScript.StdOut.Write(ByteToHex(rd) + ' ');
    if ((i+1) % lineLength == 0)
    {
      WScript.StdOut.Write(' ' + txt + '\n');
      txt = '';
    }
  }
  WScript.StdOut.Write(' ' + txt + '\n');
}

function NibbleToHex(halfByte)
{
  return String.fromCharCode(halfByte < 10 ?
      halfByte + 48 : // 0 to 9
      halfByte + 55); // A to F
}

function ByteToHex(dec)
{
  var h = (dec & 0xF0) >> 4;
  var l = dec & 0x0F;
  return NibbleToHex(h) + NibbleToHex(l);
}

function DecimalToHex(dec)
{
  var result = '';
  do
  {
    result = ByteToHex(dec & 0xFF) + result;
    dec >>= 8;
  } while (dec > 0);
  return result;
}

输出:

Found 350 bytes
00 30 69 71 3A 05 C8 9E 4C A6 30 CA 78 54  .0iq:.ÈL¦0ÊxT
C1 C6 C8 98 F1 18 00 C7 C6 08 C6 C6 8C 61  ÁÆÈñ..ÇÆ.ÆÆa
6C 0C F0 A7 38 6F 2C 20 20 00 8C 01 60 0C  l.ð§8o,  ..`.
05 C6 2A 38 81 17 60 C0 D8 18 5E 00 00 63  .Æ*8.`ÀØ.^..c
63 78 01 00 8C 8D 01 4C 55 14 60 FC E3 31  cx...LU.`üã1
C0 07 30 06 8C 11 60 0C 00 8C 3D 80 F1 1E  À.0..`..=ñ.
00 40 41 F1 A0 64 3C 23 54 4B 0E B0 D3 78  .@Añ d<#TK.°Óx
54 61 C6 C8 98 F1 18 00 C7 C6 08 C6 C6 8C  TaÆÈñ..ÇÆ.ÆÆ
61 6C 0C F0 A7 38 6F 6C FA 04 00 8C 01 60  al.ð§8olú...`
0C 05 C6 2A 38 81 17 60 C0 D8 18 5E 00 00  ..Æ*8.`ÀØ.^..
63 63 78 01 00 8C 8D 01 4C 55 14 60 FC E3  ccx...LU.`üã
31 C0 07 30 06 8C 11 60 0C 00 8C 3D 80 31  1À.0..`..=1
1E 00 40 41 F1 A0 64 3C 23 54 4B 0E 30 5A  ..@Añ d<#TK.0Z
95 54 C1 C6 C8 98 F1 18 00 C7 C6 08 C6 C6  TÁÆÈñ..ÇÆ.ÆÆ
8C 61 6C 0C F0 A7 38 6F 2C 20 20 00 8C 01  al.ð§8o,  ..
60 0C 05 C6 2A 38 81 17 60 C0 D8 18 5E 00  `..Æ*8.`ÀØ.^.
00 63 63 78 01 00 8C 8D 01 4C 55 14 60 FC  .ccx...LU.`ü
E3 31 C0 07 30 06 8C 11 60 0C 00 8C 3D 80  ã1À.0..`..=
F1 1E 00 40 41 F1 A0 64 3C 23 54 4B 0E B0  ñ..@Añ d<#TK.°
CC 78 8C C3 C6 C8 98 F1 18 00 C7 C6 08 C6  ÌxÃÆÈñ..ÇÆ.Æ
C6 8C 61 6C 0C F0 A7 00 30 66 7A 63 C3 1B  Æal.ð§.0fzcÃ.
03 60 0C 05 C6 2A 38 81 17 60 C0 D8 18 5E  .`..Æ*8.`ÀØ.^
00 00 63 63 78 01 00 8C 8D 01 4C 55 14 60  ..ccx...LU.`
FC E3 31 C0 07 30 06 8C 11 60 0C 00 8C 3D  üã1À.0..`..=
BC 31 06 00 40 41 F1 A0 64 3C 23 54 4B 0E  ¼1..@Añ d<#TK.

以防万一,另一种解包算法:

// Here the 8 bits of 7 bytes of raw data are coded as 7 bytes of data stripped off of the high bit,
// while the stripped bits are grouped in the first byte of the data run.
// In other words, when we have a run of 8 bytes, the first one groups the high bits of the 7 next bytes.
// Information found at http://crystal.apana.org.au/ghansper/midi_introduction/file_dump.html

var headerLength = 7;
var resultData = new Array();
var decodedByteCount = 0;  // Number of expanded bytes in result
var runCount = -1; // Number of runs in the encoded data
for (var i = headerLength; // Skip header
    i < midiData.length - 1; // Omit EOF
    i++)
{
  var rank = (i - headerLength) % 8; // We split the data in runs of 8 bytes
  if (rank == 0)  // Start of the run
  {
    // Get the high bits
    var highBits = midiData[i];
    runCount++;
//~     WScript.StdOut.Write(runCount + ' > ' + (i - 7) + ' >> ' + ByteToHex(highBits) + '\n');
  }
  else
  {
    resultData[decodedByteCount++] = midiData[i] |
        ((highBits & (1 << (7 - rank))) << rank);
//~     WScript.StdOut.Write((i - 7) + ' >> ' +  ByteToHex(midiData[i]) + ' > ' +
//~         ByteToHex(midiData[i] | ((highBits & (1 << (7 - rank))) << rank)) + '\n');
  }
}
于 2008-12-01T16:23:19.087 回答
0

感谢您的出色工作,我想出了这个作为包算法。似乎 Alesis 使用与 Moog Voyager 相同的架构。

packSysex : function(midiData) {
    var header = [0xF0, 0x04, 0x01, 0x00, 0x03, 0x00]; //Voyager Single Preset Dump.

    var resultData = new Array();
    var packedByteCount = 0;
    var bitCount = 0;

    var thisByte;
    var packedByte;
    var nextByte = 0x0;


    for (var i = 0; i <= midiData.length; i++)
    {
        thisByte = midiData[i];
        packedByte = ((thisByte << bitCount) | nextByte) & 0x7F;
        nextByte = midiData[i] >> (7-bitCount);

        resultData[packedByteCount++] = packedByte;

        bitCount++;
        if(bitCount >= 7) {
            bitCount = 0;

            //Fill last byte
            packedByte = nextByte & 0x7F;
            resultData[packedByteCount++] = packedByte;
            nextByte = 0x0;
        }
    }

    resultData[packedByteCount++] = 0xF7;
    resultData = header.concat(resultData);

    return resultData;
},
于 2016-04-30T14:32:55.737 回答