RubyでMIDIファイル内のコードネームを取得する
MIDIlibについて
また需要のない記事を書きます。
MIDIファイルを操作したいな〜って思って、
まずPythonを探したんだけどなんか読みにくくて
(勉強不足)
rubyで探したら[midilib]なるよさ気なGemを見つけたので、使ってみた。
XFフォーマットについて
今回MIDIファイル内のコードネームを取得したかったのですが、
現状のMIDIlibではできない。
//なお、おまえXGWorksつかってんのwってツッコミは一切受け付けていません。
というのも、そもそもコードネームはGMでは規格外で、
XF拡張仕様のよう。
(そういえばMIDI検定にも出てこなかった。)
そりゃ標準ライブラリでは対応してないわけです。
下記リンク先のPDF22ページ目、
「Chord Name」をご参照ください。
オーバライド
ソースもわかりやすいし、
examples/にいろいろサンプルスクリプトがあるので、
割と直感的にいじれそう。
なにはともあれできたソースがこれ↓
#! /usr/bin/env ruby # # usage: seq2text.rb [midi_file] # # This script translates a MIDI file into text. It reads the file into # a MIDI::Sequence and calls to_s for each event. # # For a different (and more verbose) way to do the same thing, see # reader2tex.rb. # # Start looking for MIDI module classes in the directory above this one. # This forces us to use the local copy, even if there is a previously # installed version out there somewhere. $LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib') require 'midilib/sequence' require 'pry' DEFAULT_MIDI_TEST_FILE = 'init2.mid' module MIDI META_CHORD = 0x437b01 ACCIDENTS = ["bbb", "b", "b", "", "#", "##", "###"] NOTES = ["", "C", "D", "E", "F", "G", "A", "B"] CHORDS = %w(Maj Maj6 Maj7 Maj7(#11) Maj(9) Maj7(9) Maj6(9) aug min min6 min7 min7b5 min(9) min7(9) min7(11) minMaj7 minMaj7(9) dim dim7 7th 7sus4 7b5 7(9) 7(#11) 7(13) 7(b9) 7(b13) 7(#9) Maj7aug 7aug 1+8 1+5 sus4 1+2+5) class Chord < MetaEvent # Constructor def initialize(chord_root, chord_type, base_note, base_chord, delta_time = 0) super(META_CHORD, [chord_root, chord_type, base_note, base_chord], delta_time) end def to_s root_chars = @data[0].to_s(16).rjust(2).chars.map{|char| char.to_i(16)} root = [NOTES[root_chars[1]], ACCIDENTS[root_chars[0]]].join type = CHORDS[@data[1]] "#{delta_time}: #{root}#{type}" end end module IO class MIDIFile def chord(chord_root, chord_type, base_note, base_chord) end def meta_event(type) m = msg() # Copy of internal message buffer # Create raw data array @raw_data = [] @raw_data << META_EVENT @raw_data << type @raw_data << @raw_var_num_data @raw_data << m @raw_data.flatten! case type when META_SEQ_SPECIF ms = m[0..2].map{|m_| m_.to_s(16).rjust(2, "0")}.join.to_i(16) if ms == META_CHORD chord(*m[3..-1]) end when META_SEQ_NUM sequence_number((m[0] << 8) + m[1]) when META_TEXT, META_COPYRIGHT, META_SEQ_NAME, META_INSTRUMENT, META_LYRIC, META_MARKER, META_CUE, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f text(type, m) when META_TRACK_END eot() when META_SET_TEMPO tempo((m[0] << 16) + (m[1] << 8) + m[2]) when META_SMPTE smpte(m[0], m[1], m[2], m[3], m[4]) when META_TIME_SIG time_signature(m[0], m[1], m[2], m[3]) when META_KEY_SIG key_signature(m[0], m[1] == 0 ? false : true) when META_SEQ_SPECIF sequencer_specific(type, m) else meta_misc(type, m) end end end class SeqReader def chord(chord_root, chord_type, base_note, base_chord) @track.events << Chord.new(chord_root, chord_type, base_note, base_chord, @curr_ticks) end end end end # Read from MIDI file seq = MIDI::Sequence.new() File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb') do |file| # The block we pass in to Sequence.read is called at the end of every # track read. It is optional, but is useful for progress reports. seq.read(file) do |track, num_tracks, i| puts "read track #{track ? track.name : ''} (#{i} of #{num_tracks})" end end seq.each do |track| puts "*** track name \"#{track.name}\"" puts "instrument name \"#{track.instrument}\"" puts "#{track.events.length} events" track.each do |e| e.print_decimal_numbers = true # default = false (print hex) e.print_note_names = true # default = false (print note numbers) puts e end end
「/samples/seq2text.rb」をごりごりした。
実行するとこんな感じ。
$ ruby -S seq2text.rb midi_file_name.mid read track (0 of 1) read track �V�̊ϑ� (1 of 1) *** track name "�V�̊ϑ�" instrument name "" 463 events time sig 4/4 tempo 363636 msecs per qnote (165.000165000165 bpm) key sig C major 0: meta 3 sequence or track name: �V�̊ϑ� 0: sys ex 0: ch 0 cntl 121 0 0: ch 0 cntl 120 0 0: ch 0 cntl 123 0 240: ch 0 cntl 0 0 0: ch 0 cntl 32 0 0: ch 0 prog 81 240: ch 0 cntl 7 127 240: ch 0 cntl 11 120 240: ch 0 pb 8192 240: ch 0 cntl 72 64 240: ch 0 cntl 71 64 240: ch 0 cntl 1 0 480: ch 0 cntl 10 64 720: ch 0 cntl 101 0 40: ch 0 cntl 100 0 40: ch 0 cntl 6 12 880: CMaj 15360: FMaj 15360: CMaj 15360: FMaj 15360: GMaj 3360: FMaj 3840: ch 0 on D4 107 440: ch 0 off D4 64 40: CMaj(9) 0: ch 0 on D4 105 1360: ch 0 off D4 64 80: ch 0 on E4 108 440: ch 0 off E4 64 40: ch 0 on E4 107 1400: ch 0 off E4 64 40: ch 0 on C4 98 440: ch 0 off C4 64 40: ch 0 on D4 100 440: ch 0 off D4 64 40: ch 0 on E4 103 440: ch 0 off E4 64 40: ch 0 on F4 110 920: ch 0 off F4 64 40: ch 0 on E4 103 640: ch 0 cntl 11 118 160: ch 0 cntl 11 117 80: ch 0 cntl 11 116 ・・・・以下略
なんの曲かわかった人がいたらコメントください。
(二次利用の都合上、CMajにキー変えています。)