Last active
August 29, 2015 14:14
-
-
Save monomadic/b89e5ea914ed3296acb1 to your computer and use it in GitHub Desktop.
Batch split multi-part MIDI files into single-part MIDI files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'fileutils' | |
require 'midilib/sequence' | |
require 'midilib/io/midifile' | |
DEBUG = false | |
LOGGING = true | |
LOG_FILE = Dir.pwd + '/error.log' | |
INPUT_DIR = './vgm/' | |
OUTPUT_DIR = '../split' | |
def log_error error | |
puts error_text(error) | |
File.open(LOG_FILE, 'a') {|f| f.write(error) } if LOGGING | |
end | |
def fetch_instrument_name track | |
program_change = track.events.select{ |event| event.class == MIDI::ProgramChange }.first | |
if program_change.nil? | |
if track.instrument.nil? || track.instrument.empty? || track.instrument == 'Unnamed' | |
if track.name.nil? || track.name.empty? || track.name == 'Unnamed' | |
return 'no name' | |
else | |
return track.name | |
end | |
else | |
track.instrument | |
end | |
else | |
MIDI::GM_PATCH_NAMES[program_change.program] | |
end | |
end | |
def read_midi_to_sequence(filename) | |
seq = MIDI::Sequence.new() | |
File.open(filename, 'rb') do |file| | |
begin | |
seq.read(file) | |
rescue | |
log_error "[!] error reading file: #{filename}\n" | |
if DEBUG | |
require 'pry'; binding.pry | |
end | |
end | |
end | |
return seq | |
end | |
def success_text string | |
"\033[32m#{string}\033[0m" | |
end | |
def warn_text string | |
"\033[33m#{string}\033[0m" | |
end | |
def error_text string | |
"\033[41m#{string}\033[0m" | |
end | |
def sanitize_filename(filename) | |
fn = filename.downcase.split /(?<=.)\.(?=[^.])(?!.*\.[^.])/m | |
fn.map! { |s| s.gsub /[^a-z0-9\-]+/i, '_' } | |
return fn.join '.' | |
end | |
def write_dir_unless_exists(dir) | |
unless Dir.exist?(dir) | |
FileUtils.mkpath dir | |
puts success_text("[+] mkdir #{dir}") | |
end | |
end | |
def write_track_to_file track, filename, tempo | |
# require 'pry'; binding.pry | |
if track.nil? || track.events.count == 0 || !track.events.map(&:class).include?(MIDI::NoteOn) | |
puts warn_text("[!] empty track found. skipping.") | |
else | |
# seq = MIDI::Sequence.new() | |
seq = @seq.clone | |
seq.tracks = [] | |
# add meta data | |
# meta_track = MIDI::Track.new(seq) | |
# seq.tracks << meta_track | |
# meta_track.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(tempo.round)) | |
# meta_track.events << MIDI::MetaEvent.new(MIDI::META_SEQ_NAME, 'Sequence Name') | |
notes_track = MIDI::Track.new(seq) | |
seq.tracks << notes_track | |
notes_track.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(tempo.round)) | |
notes_track.events << MIDI::MetaEvent.new(MIDI::META_SEQ_NAME, fetch_instrument_name(track)) | |
notes_track.merge(track.events) | |
begin | |
file = File.open(filename, 'wb') | |
seq.write(file) | |
# file.close | |
rescue | |
log_error "[!] error writing file: #{filename}\n" | |
if DEBUG | |
require 'pry'; binding.pry | |
end | |
end | |
puts success_text("[+] wrote #{filename}") | |
end | |
end | |
FileUtils.cd(INPUT_DIR, verbose: true) | |
Dir.glob('**/*.{mid,MID}') do |file| | |
@seq = read_midi_to_sequence(file) | |
write_dir_unless_exists(OUTPUT_DIR) | |
dirname = "#{OUTPUT_DIR}/" + file.downcase.split('.mid').first + "-#{@seq.tempo.round}bpm" | |
write_dir_unless_exists(dirname) | |
@seq.tracks.each_with_index do |track, idx| | |
instrument_name = fetch_instrument_name(track) | |
filename = "#{idx}-#{instrument_name}" | |
write_track_to_file(track, "#{dirname}/#{sanitize_filename(filename)}.mid", @seq.tempo) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment