lighttroupe/luz

View on GitHub
utils/midi_file_reader.rb

Summary

Maintainability
A
2 hrs
Test Coverage
#
# MIDIFileReader parses .midi files into an array of Samples (see SamplePlayer)
#
require 'rubygems'
require 'midilib/io/seqreader'
require 'midilib/sequence'

class MIDIFileReader
    Sample = Struct.new(:time, :name, :value)

    attr_reader :samples, :beats_per_minute

    def initialize
        @samples = []
        @beats_per_minute = 120        # set for real when a file is loaded
    end

    # Factories for making input messages in a garbage friendly way
    $midi_key_channel_and_key_to_string ||= Hash.new { |channel_hash, channel_key| channel_hash[channel_key] = Hash.new { |note_hash, note_key| note_hash[note_key] = sprintf("MIDI / Channel %02d / Key %03d", channel_key, note_key) }}
    $midi_key_channel_and_slider_to_string ||= Hash.new { |channel_hash, channel_key| channel_hash[channel_key] = Hash.new { |slider_hash, slider_key| slider_hash[slider_key] = sprintf("MIDI / Channel %02d / Slider %03d", channel_key, slider_key) }}

    def load_file(file_path)
        return false unless File.exists? file_path

        start_time = Time.now.to_f

        # Create a new, empty sequence.
        seq = MIDI::Sequence.new

        # Read the contents of a MIDI file into the sequence.
        File.open(file_path, 'rb') { |file|
            seq.read(file)
        }

        # Store this
        @beats_per_minute = seq.beats_per_minute

        samples_per_minute = (@beats_per_minute * seq.ppqn)
        samples_per_second = (samples_per_minute / 60.0)

        puts "MIDI format: #{seq.format}, BPM:#{@beats_per_minute}, Tempo:#{seq.tempo}, QNotes: #{seq.qnotes}, PPQN: #{seq.ppqn}, #{seq.numer}/#{seq.denom}, samples per second: #{samples_per_second}"

        sort_needed = false
        seq.each_with_index { |track, index|
            sort_needed = true if index > 0
            track.each { |event|
                case event
                when MIDI::NoteOn
                    # ["<=>", "channel", "channel=", "channel_to_s", "data_as_bytes", "delta_time", "delta_time=", "note", "note=", "note_to_s", "number_to_s", "off", "off=", "pch_oct", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "quantize_to", "status", "time_from_start", "time_from_start=", "velocity", "velocity="]
                    @samples << Sample.new(event.time_from_start / samples_per_second, $midi_key_channel_and_key_to_string[event.channel + 1][event.note], event.velocity / 127.0)

                when MIDI::NoteOff
                    # ["<=>", "channel", "channel=", "channel_to_s", "data_as_bytes", "delta_time", "delta_time=", "note", "note=", "note_to_s", "number_to_s", "off", "off=", "pch_oct", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "quantize_to", "status", "time_from_start", "time_from_start=", "velocity", "velocity="]
                    @samples << Sample.new(event.time_from_start / samples_per_second, $midi_key_channel_and_key_to_string[event.channel + 1][event.note], 0.0) 

                when MIDI::Controller
                    # ["<=>", "channel", "channel=", "channel_to_s", "controller", "controller=", "data_as_bytes", "delta_time", "delta_time=", "number_to_s", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "quantize_to", "status", "time_from_start", "time_from_start=", "value", "value="]
                    @samples << Sample.new(event.time_from_start / samples_per_second, $midi_key_channel_and_slider_to_string[event.channel + 1][event.controller], event.value / 127.0) 

                when MIDI::Tempo
                    # ["<=>", "channel_to_s", "data", "data=", "data_as_bytes", "data_as_str", "delta_time", "delta_time=", "meta_type", "number_to_s", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "quantize_to", "status", "tempo", "tempo=", "time_from_start", "time_from_start="]
                    #p event

                #when MIDI::TimeSig
                    # ["<=>", "channel_to_s", "data", "data=", "data_as_bytes", "data_as_str", "delta_time", "delta_time=", "denominator", "measure_duration", "meta_type", "metronome_ticks", "number_to_s", "numerator", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "quantize_to", "status", "time_from_start", "time_from_start="]
                #when MIDI::KeySig
                    # ["<=>", "channel_to_s", "data", "data=", "data_as_bytes", "data_as_str", "delta_time", "delta_time=", "major_key?", "meta_type", "minor_key?", "number_to_s", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "quantize_to", "sharpflat", "status", "time_from_start", "time_from_start="]
                #when MIDI::MetaEvent
                    # ["<=>", "channel_to_s", "data", "data=", "data_as_bytes", "data_as_str", "delta_time", "delta_time=", "meta_type", "number_to_s", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "quantize_to", "status", "time_from_start", "time_from_start="]
                #when MIDI::ProgramChange
                    # ["<=>", "channel", "channel=", "channel_to_s", "data_as_bytes", "delta_time", "delta_time=", "number_to_s", "print_channel_numbers_from_one", "print_channel_numbers_from_one=", "print_decimal_numbers", "print_decimal_numbers=", "print_note_names", "print_note_names=", "program", "program=", "quantize_to", "status", "time_from_start", "time_from_start="]

                end
            }
        }
        sort! if sort_needed
        puts "Loaded #{@samples.size} samples, first at #{@samples.empty? ? '(none)' : @samples.first.time} in #{Time.now.to_f - start_time} seconds"
        true
    end

    def sort!
        puts "Sorting Samples..."
        @samples.sort! { |s1, s2| s1.time <=> s2.time }
    end
end