class Song

encoding: utf-8

Attributes

file[RW]
info[RW]
melody[RW]
order[RW]
verses[RW]

Public Class Methods

capo(chord, fret) click to toggle source
# File Creator/capo.rb, line 10
def self.capo(chord, fret)
  idx = OCTAVE.index(chord[0..1])
  range = 0..1
  if idx.nil? then
    idx =  OCTAVE.index(chord[0])
    range = 0
  end
  raise StandardError.new("Unknown Chords #{chord}!") if idx.nil?

  chord[range] = OCTAVE[(idx + fret) % OCTAVE.size]
  return chord
end
new() click to toggle source
# File Creator/song.rb, line 4
def initialize
  @verses = []
  @info = {}
end
parse(f) click to toggle source
# File Creator/parser.rb, line 98
def self.parse(f)
  s = Song.new
  s.parse_song_chords(f)
  s.parse_abc
  return s
end
split_chords(line) click to toggle source
# File Creator/parser.rb, line 13
def self.split_chords(line)
  return line.enum_for(:scan, /\b\w+\b/).map{[Regexp.last_match[0], Regexp.last_match.begin(0)]}
end

Public Instance Methods

author() click to toggle source
# File Creator/song.rb, line 12
def author; info["author"] || info["composer"] || info["C"] || "Traditional"; end
capo!(fret) click to toggle source
# File Creator/capo.rb, line 2
def capo!(fret)
  capo_melody!(fret) unless melody.nil? or melody.empty?
  verses.each{|v| v.capo!(fret)}
  %w[key K].each{|k| info[k] = Song.capo(info[k], fret) if info.include?(k)}
  return self
end
desc() click to toggle source
# File Creator/song.rb, line 10
def desc; (info["desc"] || info["N"]).to_s.gsub("'", "’"); end
emoji() click to toggle source
# File Creator/song.rb, line 13
def emoji; info.fetch("emoji", "").strip.split(""); end
eps!() click to toggle source
# File Creator/latex_formatter.rb, line 57
def eps!
  return nil if melody.nil? or melody.empty?
  Open3.popen2("abcm2ps -E -O #{slug} -") do |i,o,t|
    other_info = []
    # other_info << "%%textfont Times-Italic 14"
    # other_info << "%%text Key of #{self.key}"
    # other_info << "%%textoption right"
    # other_info << "%%text #{self.author}"
    i.print(melody.sub(/^([^:]*)$/, "#{other_info.join("\n")}\\1").to_s)
    i.close
    t.join
  end
  return "#{slug}001.eps"
end
hidden?() click to toggle source
# File Creator/song.rb, line 19
def hidden?; info['skip'] == "true"; end
key() click to toggle source
# File Creator/song.rb, line 11
def key; (info["key"] || info["K"]).to_s.gsub("maj", "").gsub("min", "m"); end
parse_abc(f = nil) click to toggle source
# File Creator/parser.rb, line 17
def parse_abc(f = nil)
  f ||= "#{File.dirname(file)}/#{slug}.abc"
  return log.debug("No such file #{f}") unless File.exist?(f)
  remove_sections = %w[T Q C N]
  self.melody = File.read(f)
  self.file = File.absolute_path(f) if file.nil?
  self.info.merge!(self.melody.each_line.map{|l|l.split(":")}.reject{|i|i.size != 2}.inject({}){|h,k| h[k[0]]=k[1].strip; h})
  self.melody = self.melody.each_line.reject{|l| remove_sections.include?(l.split(":").first)}.join
  return self
rescue
  log "Could not parse ABC for #{f}"
  raise
end
parse_song_chords(f) click to toggle source
# File Creator/parser.rb, line 31
def parse_song_chords(f)
  state = :none
  last_key = :text
  current_line = Song::Line.new
  self.file = File.absolute_path(f)
  File.open(f).each_line do |raw_line|
    line = raw_line.strip
    # log.debug("#{state}: #{line[0..20]}")
    case (state)
    when :none then
      if line == "---" then
        log.debug "Starting header"
        state = :header
        next
      end

      if line.start_with?("Additional Verses") then
        next
      end

      unless line.empty? then
        log.debug "Starting new verse"
        state = :verse
        self.verses << Song::Verse.new
        redo
      end
    when :header then
      if line == "---" then
        log.debug "Ending header"
        state = :none
        next
      end

      key, value = line.scan(/(\w*):(.*)/).flatten
      log.debug("k: #{key}, v:#{value}, lk: #{last_key}")
      if key.nil? or value.nil? then
        info[last_key] += " " + line.strip
      elsif last_key
        info[key] = value.strip
        last_key = key
      end
    when :verse then
      if line.empty? then
        state = :none
        next
      end

      if line.match(/^\[?[Cc]horus:?\]?$/) then
        self.verses.last.chorus = true
        next
      end

      if raw_line.gsub('\r', '').match(/[ \t]{2}/) then
        current_line.raw_chords = raw_line.gsub('\t', '  ').gsub('\r', '').gsub('\n', '')
        current_line.chords = Song.split_chords(raw_line.gsub('\t', '  '))
        # log.debug "Chords #{current_line.chords}"
      else
        current_line.lyrics = line
        self.verses.last.lines << current_line
        current_line = Song::Line.new
      end
    end
  end

  return self
end
preview_latex() click to toggle source
# File Creator/latex_formatter.rb, line 72
def preview_latex
  Dir.chdir("/tmp/") { eps! }
  File.write("/tmp/preview.tex", SongBook.templatize(to_latex))

  Dir.chdir("/tmp") do
    return unless system("xelatex -shell-escape /tmp/preview.tex")
  end
  spawn("evince /tmp/preview.pdf")
end
short?() click to toggle source
# File Creator/song.rb, line 20
def short?; info['short'] == 'true'; end
slug() click to toggle source
# File Creator/song.rb, line 15
def slug; File.basename(file, File.extname(file)); end
song?() click to toggle source
# File Creator/song.rb, line 17
def song?; return !tune?; end
title() click to toggle source
# File Creator/song.rb, line 9
def title; (info["title"] || info["T"]).to_s.gsub("'", "’"); end
to_html() click to toggle source
# File Creator/html_formatter.rb, line 39
def to_html
  verses.map(&:to_html).join("\n\n")
end
to_latex() click to toggle source
# File Creator/latex_formatter.rb, line 34
def to_latex
  parts = []
  case
  when short? then parts << "\\newshortsong"
  when song? then parts << "\\newsong"
  when tune? then parts << "\\newtune"
  end
  parts << "\\section{#{title}}"
  parts << "\\songdesc{#{desc}}\n\n" if desc
  parts << "\\#{(melody.nil? or melody.empty?) ? "nm" : ""}songinfo{#{key.empty? ? "No Key Specified" : "Key of #{key}"}}{#{author}}"
  parts << "\\melody{#{slug}001.eps}" unless melody.nil? or melody.empty?
  # parts << "\\begin{abc}[name=#{title.downcase.gsub(/[^\w]/, "")}]\n#{melody}\\end{abc}" unless melody.nil? or melody.empty?
  # parts << "\\begin{lilypond}[quoted,staffsize=26]\n#{lilypond}\\end{lilypond}" unless melody.nil? or melody.empty?

  unless verses.empty? then
    parts << "\\begin{lyrics}"
    parts << verses.collect{|v| v.to_latex}.join("\n")
    parts << "\\end{lyrics}"
  end

  return parts.join("\n")
end
to_midi() click to toggle source
# File Creator/midi_formatter.rb, line 2
def to_midi
  return nil if melody.nil? or melody.empty?
  Open3.popen2("abc2midi - -o #{slug}.mid") do |i,o,t|
    other_info = []
    # other_info << "%%textfont Times-Italic 14"
    # other_info << "%%text Key of #{self.key}"
    # other_info << "%%textoption right"
    # other_info << "%%text #{self.author}"
    i.print(melody.sub(/^([^:]*)$/, "#{other_info.join("\n")}\\1").to_s)
    i.close
    t.join
  end
  return "#{slug}.mid"
end
tune?() click to toggle source
# File Creator/song.rb, line 18
def tune?; return verses.empty?; end

Private Instance Methods

capo_melody!(fret) click to toggle source
# File Creator/capo.rb, line 24
def capo_melody!(fret)
  Open3.popen2("abc2abc - -t #{fret}") do |i,o,t|
    i.print(melody.to_s)
    i.close
    t.join
    self.melody = o.read
  end
end