vsqx2midi111.py - VOCALOID3 の vsqx を MIDI と歌詞テキストに変換する(マルチパート対応)


ですが、トラックが複数のパートに分かれている場合に対応していませんでしたので、改良して直しました

# 2012-01-07 ver.1.1.1  マルチパートのトラックに対応

import xml.dom.minidom
import struct

FILENAME = 'hoge'  # 対象のファイル名

def chrs(s):
    return ''.join([chr(x) for x in s])

def i2(v):
    if v > 0xffff:
        raise ValueError(v)
    return struct.pack('>H', v)

def i4(v):
    if v > 0xffffffff:
        raise ValueError(v)
    return struct.pack('>I', v)

def delta(v):
    if v < 0:
        raise ValueError(v)
    a = []
    while True:
        if v <= 0x7f:
            a.append(v)
            break
        else:
            a.append(v % 0x80)
            v //= 0x80
    a.reverse()
    return ''.join([chr(x + 0x80) for x in a[0:-1]] + [chr(a[-1])])

def notedata(n, tag):
    return n.getElementsByTagName(tag)[0].firstChild.data

def makenote(n):
    return dict([(x, notedata(n, x)) for x in
                 ('posTick', 'durTick', 'noteNum',
                  'velocity', 'lyric', 'phnms')])

def noteon(ch, nn, vel):
    return chr(0x90 + ch) + chr(nn) + chr(vel)

def noteoff(ch, nn):
    return chr(0x80 + ch) + chr(nn) + chr(0)

def maketrack(n):
    track = []
    if n.nodeName == 'note':
        track.append(makenote(n))
    if n.childNodes:
        for i in n.childNodes:
            track += maketrack(i)
    return track

def makemiditrack(n, ch):
    ch %= 16  # 16トラックを超えたら 0 に戻る
    midi = []
    lyric = []
    tick = 0
    for p in n.getElementsByTagName('musicalPart'):
        postick = int(notedata(p, 'posTick'))
        for i in maketrack(p):
            t = postick + int(i['posTick'])
            notelen = int(i['durTick'])
            midi.append(
                delta(t - tick) +
                noteon(ch, int(i['noteNum']), int(i['velocity'])) +
                delta(notelen) +
                noteoff(ch,  int(i['noteNum'])))
            lyric.append(i['lyric'])
            tick = t + notelen
    data = ''.join(midi) + delta(0) + chrs([0xff, 0x2f, 0])
    return 'MTrk' + i4(len(data)) + data, ' '.join(lyric)

def makemidifile(filename):
    print 'loading:', filename
    d = xml.dom.minidom.parse(filename)
                
    tracks = []
    lyrics = []
    for n, i in enumerate(d.getElementsByTagName('vsTrack')):
        print 'track', n
        track, lyric = makemiditrack(i, n)
        tracks.append(track)
        lyrics.append(lyric)
    header = 'MThd' + i4(6) + i2(1) + i2(len(tracks)) + i2(480)
    return header + ''.join(tracks), '\n\n'.join(lyrics)

midi, lyric = makemidifile(FILENAME + '.vsqx')
f = open(FILENAME + '.mid', 'wb')
f.write(midi)
f.close()
f = open(FILENAME + '.txt', 'w')
f.write(lyric.encode('utf_8_sig'))
f.close()

## パブリックドメイン。好きに流用してください