Tab Player Source Code
play.rb
#!/usr/bin/ruby
require 'guitar'
require 'tab'
require 'optparse'
require 'ostruct'
options = OpenStruct.new
options.debug = false
options.speed_factor = 1.0
options.tab_file = nil
options.midi_file = nil
options.guitar_type = Guitar::NYLON_ACOUSTIC
opts = OptionParser.new do |opts|
opts.banner = "Usage: play.rb [options]"
opts.separator ""
opts.on("-t", "--tab-file TAB_FILE",
"Set input tablature file. If not specified, STDIN is read.") do |ifile|
options.tab_file = ifile
end
opts.on("-m", "--midi-file MIDI_FILE",
"Set output midi file. If not specified, STDOUT is written.") do |mfile|
options.midi_file = mfile
end
opts.on("-s", "--speed-factor SPEED_FACTOR",
"Set speed factor. < 1 = slower, > 1 = faster.") do |sf|
options.speed_factor = sf.to_f
end
opts.on("-g", "--guitar-type GUITAR_TYPE",
"Set type of guitar: n = nylon acoustic (default), s = steel acoustic",
" j = jazz electric, c = clean electric, m = muted electric",
" o = overdriven electric, d = distorted electric",
" h = harmonics") do |gt|
options.guitar_type = Guitar.type_for_string(gt)
end
opts.on("-d", "--debug",
"Turn on debug mode. Debug info is written to stdout,", "must use with -m switch.") do |dm|
options.debug = dm
end
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit 1
end
end
opts.parse!(ARGV)
if options.debug && options.midi_file.nil?
puts "Midi file must be written to file using -m switch when -d debug mode is used."
puts opts
exit 1
end
istream = STDIN
if ! options.tab_file.nil?
istream = File.new(options.tab_file)
end
TODO
axe = Guitar.new(options.guitar_type, Guitar::EADGBE, (140*options.speed_factor).to_i, "eighth")
ostream = STDOUT
if ! options.midi_file.nil?
ostream = File.new(options.midi_file, "w+")
end
tab = Tab.new(options.debug)
tab.parse(istream)
tab.play(axe, ostream)
tab.rb
class Tab
@@notes = [ 's', 'e', 'q', 'h', 'w']
def initialize(debug_mode)
@debug = debug_mode
@tabs = Array.new
end
def parse(instream)
strings = nil
string_count = 0
while line = instream.gets
line.chomp!
if !(line =~ /^[BbGgDdAaEe\d]?[\|\) \:]?[-x<> \[\]\(\)\{\}\*\=\^\/\\~\w\.:\|\d]+$/) || line.index("-").nil?
if ! strings.nil?
@tabs << strings
end
if @debug
puts "re does not match: #{line}"
end
strings = nil
string_count = 0
next
end
line.sub!(/^[BbGgDdAaEe\d]?[\|\) \:]?/, "")
line.sub!(/([\|\-]) .+$/, '\1')
line.gsub!(/[\|\:]/, "")
line.gsub!(/O/, "0");
line.gsub!(/[^\d]/, "x")
if strings.nil?
strings = Array.new
end
if strings[string_count].nil?
strings[string_count] = line
else
strings[string_count] += line
end
string_count += 1
end
@tabs << strings if not strings.nil?
end
def play(axe, ostream)
@tabs.each { |ss|
if @debug
puts "strings:\n#{ss.join("\n")}"
end
if ss.length() != 6
next
end
if @debug
puts "PLAYING these lines!"
end
num_eighths = 1000000
ss.each { |str|
if str.length < num_eighths
num_eighths = str.length
end
}
delay_index = -1
empty_chord = 'x|' * ss.size()
i = 0
while i < num_eighths - 1
chord = ''
max_number_length = 1
ss.size().downto(1) { |s|
this_max_number_len = 1
if ss[s-1][i].chr != "x" && ss[s-1][i+1].chr != "x"
this_max_number_len += 1
end
if this_max_number_len > max_number_length
max_number_length = this_max_number_len
end
}
ss.size().downto(1) { |s|
if max_number_length > 1 && ss[s-1][i].chr == "x" && ss[s-1][i+1].chr != "x"
chord << ss[s-1][i+1]
elsif ss[s-1][i].chr != "x" && ss[s-1][i+1].chr != "x"
chord << ss[s-1][i]
chord << ss[s-1][i+1]
else
chord << ss[s-1][i]
end
chord << "|"
}
if chord == empty_chord
if delay_index + 1 < @@notes.length()
delay_index += 1
end
else
if delay_index == -1
delay_index = 0
end
chord.chomp!("|")
axe.play("#{@@notes[delay_index]}:#{chord}")
delay_index = -1
end
i += max_number_length
end
axe.play("w:x|x|x|x|x|x")
}
ostream << axe.dump
end
end
guitar.rb
require 'stringio'
begin
require 'midilib'
rescue LoadError
require 'rubygems' and retry
end
class Guitar
EADGBE = [40, 45, 50, 55, 59, 64]
DADGBE = [38, 45, 50, 55, 59, 64]
EbAbDbGbBbEb = [39, 44, 49, 54, 58, 63]
DGCFAD = [38, 43, 48, 53, 57, 62]
NYLON_ACOUSTIC = 25
STEEL_ACOUSTIC = 26
JAZZ_ELECTRIC = 27
CLEAN_ELECTRIC = 28
MUTED_ELECTRIC = 29
OVERDRIVEN_ELECTRIC = 30
DISTORTED_ELECTRIC = 31
HARMONICS = 32
def Guitar.type_for_string(t)
case t
when 'n'
return NYLON_ACOUSTIC
when 's'
return STEEL_ACOUSTIC
when 'j'
return JAZZ_ELECTRIC
when 'c'
return CLEAN_ELECTRIC
when 'm'
return MUTED_ELECTRIC
when 'o'
return OVERDRIVEN_ELECTRIC
when 'd'
return DISTORTED_ELECTRIC
when 'h'
return HARMONICS
end
return NYLON_ACOUSTIC
end
def initialize(instr = NYLON_ACOUSTIC,
tuning = EADGBE,
bpm = 140,
note = "eighth")
@tuning = tuning
@seq = MIDI::Sequence.new
@seq.tracks << (ctrack = MIDI::Track.new(@seq))
@seq.tracks << (@track = MIDI::Track.new(@seq))
@note = note
@notes = { 's' => 'sixteenth', 'e' => 'eighth', 'q' => 'quarter', 'h' => 'half', 'w' => 'whole' }
ctrack.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(bpm))
ctrack.events << MIDI::ProgramChange.new(0,instr,0)
ctrack.events << MIDI::ProgramChange.new(1,instr,0)
ctrack.events << MIDI::ProgramChange.new(2,instr,0)
ctrack.events << MIDI::ProgramChange.new(3,instr,0)
ctrack.events << MIDI::ProgramChange.new(4,instr,0)
ctrack.events << MIDI::ProgramChange.new(5,instr,0)
@prev = [nil] * 6
@prev_dist = [0] * 6
end
def play(notes)
md = /(\w):(.+)/.match(notes)
notetype = @notes[md[1]]
d = @seq.note_to_delta(notetype)
md[2].split('|').each_with_index do |fret, channel|
if fret.to_i.to_s == fret
fret = fret.to_i
oldfret = @prev[channel]
@prev[channel] = fret
if oldfret
oldnote = @tuning[channel] + oldfret
@track.events << MIDI::NoteOffEvent.new(channel,oldnote,0,d)
d = 0
end
noteval = @tuning[channel] + fret
@track.events << MIDI::NoteOnEvent.new(channel,noteval,80 + rand(38),d)
d = 0
end
end
end
def dump
out = StringIO.new
@track.recalc_times
@seq.write(out)
out.string
end
end