Skip to content

Instantly share code, notes, and snippets.

@jangler
Created June 29, 2013 21:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jangler/5892763 to your computer and use it in GitHub Desktop.
Save jangler/5892763 to your computer and use it in GitHub Desktop.
Compiles MML to MIDI
require_relative 'midifile'
TicksPerQuarter = 32
NoteOffsets = { 'c' => 0, 'd' => 2, 'e' => 4, 'f' => 5, 'g' => 7, 'a' => 9, 'b' => 11 }
class Track
attr_reader :delay, :octave, :note, :velocity, :bytes, :default_delay
attr_writer :delay, :octave, :note, :velocity, :bytes, :default_delay
def initialize()
@delay = 0
@octave = 5
@note = nil
@velocity = 0x40
@bytes = []
@default_delay = TicksPerQuarter
end
end
def error(msg)
puts msg
exit
end
def add(arr)
arr.each { |byte| $midi_bytes << byte }
end
def track_add(arr)
arr.each { |byte| $current_track.bytes << byte }
end
def parse_line(line)
i = 0
while i < line.length
if line[i] =~ /[ABCDEFGHIJKLMNOPQRSTUVWXYZ]/
if not $tracks.has_key? line[i]
$tracks[line[i]] = Track.new()
end
$current_track = $tracks[line[i]]
i += 1
elsif line[i] == 'l'
i += 1
if line[i...line.length] =~ /^(\d+)/
$current_track.default_delay = TicksPerQuarter * 4 / $1.to_i
i += $1.length
end
elsif line[i] =~ /[abcdefg]/
note_val = $current_track.octave * 12 + NoteOffsets[line[i]]
i += 1
if line[i] == '+'
note_val += 1
i += 1
elsif line[i] == '-'
note_val -= 1
i += 1
end
if $current_track.note != nil
track_add midi_event($current_track.delay, NoteOff, 0, $current_track.note, 0x40)
track_add midi_event(0, NoteOn, 0, note_val, $current_track.velocity)
else
track_add midi_event($current_track.delay, NoteOn, 0, note_val, $current_track.velocity)
end
$current_track.note = note_val
if line[i...line.length] =~ /^(\d+)/
$current_track.delay = TicksPerQuarter * 4 / $1.to_i
i += $1.length
else
$current_track.delay = $current_track.default_delay
end
dot_count = 0
delay = $current_track.delay
while line[i] == '.'
dot_count += 1
$current_track.delay += delay / (2 ** dot_count)
i += 1
end
elsif line[i] == '<'
$current_track.octave -= 1
i += 1
elsif line[i] == '>'
$current_track.octave += 1
i += 1
elsif line[i] == 'o'
i += 1
if line[i...line.length] =~ /^(\d+)/
$current_track.octave = $1.to_i
i += $1.length
end
elsif line[i] == 'v'
i += 1
if line[i...line.length] =~ /^(\d+)/
$current_track.velocity = $1.to_i
i += $1.length
end
elsif line[i] == 'r'
i += 1
track_add midi_event($current_track.delay, NoteOff, 0, $current_track.note, 0x40)
$current_track.note = nil
if line[i...line.length] =~ /^(\d+)/
$current_track.delay = TicksPerQuarter * 4 / $1.to_i
i += $1.length
else
$current_track.delay = $current_track.default_delay
end
delay = $current_track.delay
dot_count = 0
while line[i] == '.'
dot_count += 1
$current_track.delay += delay / (2 ** dot_count)
i += 1
end
elsif line[i] == 's' or line[i] == 'w'
i += 1
delay = $current_track.default_delay
if line[i...line.length] =~ /^(\d+)/
delay = TicksPerQuarter * 4 / $1.to_i
i += $1.length
end
$current_track.delay += delay
dot_count = 0
while line[i] == '.'
dot_count += 1
$current_track.delay += delay / (2 ** dot_count)
i += 1
end
elsif line[i...line.length] =~ /^@(\d+)/
track_add midi_event($current_track.delay, ProgramChange, 0, $1.to_i)
$current_track.delay = 0
i += $1.length
elsif line[i] == 't'
i += 1
if line[i...line.length] =~ /^(\d+)/
microseconds = 1000000 / ($1.to_i / 60)
arr = int_to_bytes(microseconds, 3)
track_add midi_meta_event($current_track.delay, SetTempo, 03, *arr)
$current_track.delay = 0
i += $1.length
end
else
i += 1
end
end
end
ARGV.each do |filename|
mml_text = ''
File.open(filename, 'r') do |file|
mml_text = file.read()
end
$midi_bytes = []
$tracks = {}
$tracks['A'] = Track.new()
$current_track = $tracks['A']
mml_text.each_line do |line|
if line =~ /^#TITLE +(.+)$/
data = [ $1.bytes.length ]
$1.each_byte { |byte| data << byte }
track_add midi_meta_event(0, TrackName, *data)
else
parse_line(line)
end
end
add header(MultipleTracksSync, $tracks.length, TicksPerQuarter)
for track in $tracks.values
$current_track = track
track_add midi_meta_event(0, EndTrack, 0)
add track_header(track.bytes.length)
add track.bytes
end
str = ''
$midi_bytes.each { |byte| str += byte.chr }
File.open(filename + '.mid', 'w') do |file|
file.write(str)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment