vsqx2midi121.py - VOCALOID3 の vsqx を歌詞入りMIDIに変換する(UTAU 対応)
前バージョン
では UTAU で読み込むと歌詞が一つ後ろにずれてしまう、という不具合があったので訂正しました。(使い方は前バージョンの記述参照)
ちなみに UTAU で読み込んだ時にはまず最初にテンポを設定してください。MIDI の仕様だとテンポの指定の無い時には 120 が指定されていると見なすはずなのですが、UTAU はそれが実装されていないようです。
# 2012-02-19 ver.1.2.1 UTAU 対応 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) + lyrics(i['lyric']) + delta(0) + 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 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() ## パブリックドメイン。好きに流用してください