vsqx2midi120.py - VOCALOID3 の vsqx を 歌詞入りMIDI に変換する



あなたが降る街 vocal パートMIDI - ニコニ・コモンズ』を作るため、前に作った

を改造して MIDI 内に歌詞を入れられるようにしてみました。ニコニ・コモンズが vsqx に直接対応していればこんな手間は要らないのですが…

実行するには Python 2.6 あるいはそれ以降のバージョンの Python 2.x を用意してください。

使い方

  1. Python をインストール(やり方は適当にぐぐって)
  2. 下のソースを BOM付き utf-8 で「vsqx2midi.py」という名前で保存 (メモ帳で文字コードutf-8」にして保存すればOK)
  3. そのファイルを右クリックして「Edit with IDLE」を選択
  4. VOCALOID3 Editor で適当な曲を「hoge.vsqx」という名前で保存
  5. IDLE で [F5] を押して Python スクリプトを実行
  6. hoge.mid」という名前で MIDI の出来上がり
# 2012-02-14 ver.1.2.0 歌詞対応

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 lyrics(lyric):
    s = lyric.encode('shift-jis', 'replace')
    return '\xff\x05' + chr(len(s)) + s

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(0) +
                lyrics(i['lyric']) +
                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 makeconductor():
    data = delta(0) + '\xff\x2f\x00'
    return 'MTrk' + i4(len(data)) + data

def makemidifile(filename):
    print 'loading:', filename
    d = xml.dom.minidom.parse(filename)
                
    tracks = [makeconductor()]
    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()

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

テンポ等には対応しておらず、単にノートとペロシティと歌詞にのみ対応ですので、ニコニ・コモンズなりで公開する時にはテンポ等の他に必要なデータを適当な MIDI シーケンサで付け加えてください。(VOCALOID3 Editor で読み込むだけなら、どのみちテンポ等は読み込みませんので不要ですが)