编译MIDI文件:求十进制数转Variable Length Quantity的算法
可变长度量(VLQ)的理解与实现
首先明确VLQ的核心逻辑:它是一种用多字节存储整数的紧凑格式,每个字节只用低7位存实际数据,最高位作为续位标记——1表示后面还有字节,0表示这是最后一个字节。这样小数字(0-127)用1字节,大数字自动扩展成多字节,节省MIDI文件的存储空间。
举个例子:十进制128的VLQ表示
十进制128的二进制是10000000,一共8位。按VLQ规则拆分:
- 把二进制补前导0,让总长度是7的倍数:
00000010000000(补了6个前导0)。 - 分成两个7位的块:
0000001(高位块)和0000000(低位块)。 - 给高位块的最前面加1(标记有后续字节),得到
10000001(即1_0000001);给低位块加0(标记无后续),得到00000000(即0_0000000)。 - 最终的VLQ字节序列就是这两个字节,和你给出的例子一致。
编码算法(十进制转VLQ)
直接给可落地的步骤,用代码辅助理解:
- 特殊情况:如果数值是0,直接返回
0x00。 - 把数值转成二进制字符串,去掉前缀(比如
bin(128)得到0b10000000,取后面的10000000)。 - 补前导0,让字符串长度是7的倍数(确保能分成完整的7位块)。
- 按每7位分割成块,顺序是从高位到低位。
- 每个块前面加续位标记:除了最后一个块加1,最后一个块加0。
- 把每个8位的二进制串转成十六进制字节,就是最终的VLQ序列。
Python编码实现
def decimal_to_vlq(n): if n == 0: return bytes([0x00]) # 转成二进制字符串,去掉0b前缀 bin_str = bin(n)[2:] # 补前导0,让长度是7的倍数 padding = (7 - len(bin_str) % 7) % 7 bin_str = '0' * padding + bin_str # 分割成7位的块 blocks = [bin_str[i:i+7] for i in range(0, len(bin_str), 7)] vlq_bytes = [] for idx, block in enumerate(blocks): # 非最后一个块加1,最后一个加0 prefix = '1' if idx != len(blocks)-1 else '0' vlq_byte = int(prefix + block, 2) vlq_bytes.append(vlq_byte) return bytes(vlq_bytes)
测试decimal_to_vlq(128)会返回b'\x81\x00',对应你给出的二进制表示。
解码算法(VLQ转十进制)
如果需要解析MIDI里的VLQ时长,步骤更简单:
- 初始化结果为0。
- 逐个处理每个字节:
- 取字节的低7位数据,拼接到结果中(结果先左移7位,再或上低7位数据)。
- 如果字节的最高位是0,说明这是最后一个字节,停止处理。
- 最终结果就是对应的十进制数。
Python解码实现
def vlq_to_decimal(vlq_bytes): result = 0 for byte in vlq_bytes: # 左移7位腾出空间,再加上当前字节的低7位 result = (result << 7) | (byte & 0x7F) # 最高位为0,结束 if not (byte & 0x80): break return result
测试vlq_to_decimal(b'\x81\x00')会返回128,验证正确。
内容的提问来源于stack exchange,提问作者FireTheLost
相关产品推荐
相关产品推荐

