class MTK::IO::Notation

Uses {Events::Timeline}s to generates music notation graphics with {lilypond.org/ Lilypond}. @note This class is optional and only available if you require ‘mtk/io/lilypond’. @note To make notation graphics, {lilypond.org/download.html Lilypond} must be installed

and you must follow the "Running on the command-line" instructions (found on the download page for
your operating system). If the lilypond command is not on your PATH, set the environment variable LILYPOND_PATH

Constants

LILYPOND_PATH
QUANTIZATION_INTERVAL
SYNTAX_PREFIX
SYNTAX_SUFFIX
VALID_FORMATS

Public Class Methods

new(file, options={}) click to toggle source
# File lib/mtk/io/notation.rb, line 20
def initialize(file, options={})
  @file = file
  @options = options

  @format = File.extname(file)[1..-1].downcase
  raise ArgumentError.new("Invalid file format '#{@format}'") unless VALID_FORMATS.include? @format

  @dpi = options[:dpi]
end
open(file, options={}) click to toggle source
# File lib/mtk/io/notation.rb, line 30
def self.open(file, options={})
  new(file,options)
end

Public Instance Methods

write(timeline) click to toggle source
# File lib/mtk/io/notation.rb, line 35
def write(timeline)
  lilypond_syntax = syntax_for_timeline(timeline)
  puts lilypond_syntax
  puts "_____________________________"
  Tempfile.open('mtk_lilypond') do |lilypond_file|
    Dir.mktmpdir do |tmpdir|
      # use the directory...
      #open("#{dir}/foo", "w") { ... }

      lilypond_file.write(lilypond_syntax)
      lilypond_file.flush

      cmd = ['lilypond', '-dbackend=eps', "-f#{@format}", "--output=\"#{tmpdir}\""]
      cmd << "-dresolution=#{@dpi}" if @dpi
      cmd << lilypond_file.path
      cmd = cmd.join(' ')

      puts cmd if $DEBUG
      lilypond_command_output = `#{cmd}`
      puts lilypond_command_output if $DEBUG

      output_file = Dir["#{tmpdir}/*.#{@format}"].first

      FileUtils.cp output_file, @file

      puts "Wrote #{@file}"
    end
  end
end

Private Instance Methods

syntax_for_duration(duration) click to toggle source
# File lib/mtk/io/notation.rb, line 151
def syntax_for_duration(duration)
  # TODO: handle dots, triplets, and ties of arbitrary durations
  duration = MTK::Events::Timeline.quantize_time(duration.to_f.abs, QUANTIZATION_INTERVAL)
  syntax = (4.0/duration).round
  syntax = 1 if syntax < 1
  syntax.to_s
end
syntax_for_pitch(pitch) click to toggle source
# File lib/mtk/io/notation.rb, line 134
def syntax_for_pitch(pitch)
  syntax = pitch.pitch_class.name.downcase
  if syntax.length > 0
    syntax = syntax[0] + syntax[1..-1].gsub('b','f') # .gsub('#','s') pitch class names never have '#'
  end
  oct = pitch.octave
  while oct > 3
    syntax << "'"
    oct -= 1
  end
  while oct < 3
    syntax << ","
    oct += 1
  end
  syntax
end
syntax_for_timeline(timeline) click to toggle source
# File lib/mtk/io/notation.rb, line 86
def syntax_for_timeline(timeline)
  quantized_timeline = timeline.flatten.quantize(QUANTIZATION_INTERVAL)
  last_time = 0
  last_duration = 0

  s = ''
  s << SYNTAX_PREFIX

  for time,events in quantized_timeline

    # handle rests between notes
    delta = time - last_time
    if delta > last_duration
      s << 'r'+syntax_for_duration(delta - last_duration)
      s << ' '
    end

    notes = events.find_all{|event| event.type == :note }

    if notes.length > 1
      # a chord
      s << '<'
      total_duration = 0
      for note in notes
        total_duration += note.duration
        s << syntax_for_pitch(note.pitch)
        s << ' '
      end
      s << '>'
      duration = total_duration.to_f/notes.length
      s << syntax_for_duration(duration)

    else # a single note
      note = notes.first
      s << syntax_for_pitch(note.pitch)
      duration = note.duration
      s << syntax_for_duration(duration)
    end

    last_time = time
    last_duration = duration
    s << ' '
  end

  s << SYNTAX_SUFFIX
  s
end