LANケーブルは100mしか繋がない

OSとかIoTとかRubyとかPythonのゆとり系音楽プログラマー

RubyでMIDIファイル内のコードネームを取得する

MIDIlibについて

また需要のない記事を書きます。

MIDIファイルを操作したいな〜って思って、
まずPythonを探したんだけどなんか読みにくくて
(勉強不足)  

rubyで探したら[midilib]なるよさ気なGemを見つけたので、使ってみた。

XFフォーマットについて

今回MIDIファイル内のコードネームを取得したかったのですが、
現状のMIDIlibではできない。

f:id:swan_match:20160626193101p:plain

//なお、おまえXGWorksつかってんのwってツッコミは一切受け付けていません。

というのも、そもそもコードネームはGMでは規格外で、
XF拡張仕様のよう。
(そういえばMIDI検定にも出てこなかった。)

そりゃ標準ライブラリでは対応してないわけです。

下記リンク先のPDF22ページ目、
「Chord Name」をご参照ください。

XFフォーマット仕様

オーバライド

ソースもわかりやすいし、
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にキー変えています。)