# smf.rb: Written by Tadayoshi Funaba 1998-2006
# $Id: smf.rb,v 1.37 2006-11-10 21:57:47+09 tadf Exp $

require 'smf/io'
require 'forwardable'

module SMF

  class Sequence

    def initialize(format=1, division=96, tc=nil)
      # format:0/2
      # division:1/2**15-1(if tc=nil):1/2**8-1(otherwise)
      # tc:nil,24,25,29,30
      @format, @division, @tc, @arr = format, division, tc, []
    end

    attr_accessor :format, :division, :tc

    def smpte()
      warn('smpte is deprecated; use tc') if $VERBOSE
      self.tc
    end

    def smpte=(v)
      warn('smpte= is deprecated; use tc=') if $VERBOSE
      self.tc = v
    end

    def ntrks() @arr.nitems end

    def hash() @format.hash ^ @division.hash ^ @arr.hash end
    def eql? (other) self == other end

    def == (other)
      self.class == other.class &&
	format == other.format &&
	division == other.division &&
	tc == other.tc &&
	to_a == other.to_a
    end

    def calc_set(a, b)
      sq = self.class.new(format, division, tc)
      a, b = a.to_a, b.to_a
      n = yield(a, b)
      sq.replace(n)
      sq
    end

    private :calc_set

    def + (other) calc_set(self, other){|a, b| a + b} end
    def - (other) calc_set(self, other){|a, b| a - b} end
    def & (other) calc_set(self, other){|a, b| a & b} end
    def | (other) calc_set(self, other){|a, b| a | b} end

    def * (times)
      sq = self.class.new(format, division, tc)
      sq.replace(self.to_a * times)
      sq
    end

    def << (tr)
      @arr << tr
      self
    end

    def >> (tr)
      @arr.reject!{|x| x.object_id == tr.object_id}
      self
    end

    def concat(other) replace(self + other) end

    def each
      @arr.compact.each do |tr|
	yield tr
      end
      self
    end

    def join
      sq = self.class.new(format, division, tc)
      tr = Track.new
      sq << @arr.inject(tr){|t, x| t + x}
      sq
    end

    def join!
      @arr.replace(join)
      self
    end

    def replace(another)
      if Sequence === another
	@format = another.format
	@division = another.division
	@tc = another.tc
      end
      @arr.replace(another.to_a)
      self
    end

    extend Forwardable
    include Enumerable

    de = (Array.instance_methods - self.instance_methods)
    de -= %w(assoc flatten flatten! pack rassoc transpose)
    de += %w(include? sort to_a)

    def_delegators(:@arr, *de)

    undef_method :zip

  end

  class Track

    def initialize() @arr = [] end

    def nevts() @arr.nitems end

    def hash() @arr.hash end
    def eql? (other) self == other end

    def == (other)
      self.class == other.class &&
	to_a == other.to_a
    end

    def calc_set_sub(a, b, e)
      tr = self.class.new
      n = yield(a, b).reject{|x| EndOfTrack === x}
      eot = e.select{|x| EndOfTrack === x}
      n << eot.max unless eot.empty?
      tr.replace(n)
      tr
    end

    def calc_set_maxsize(a, b, &block)
      a, b = a.to_a, b.to_a
      calc_set_sub(a, b, a + b, &block)
    end

    def calc_set_selfsize(a, b, &block)
      a, b = a.to_a, b.to_a
      calc_set_sub(a, b, a, &block)
    end

    private :calc_set_sub, :calc_set_maxsize, :calc_set_selfsize

    def + (other) calc_set_maxsize(self, other){|a, b| a + b} end
    def - (other) calc_set_selfsize(self, other){|a, b| a - b} end
    def & (other) calc_set_selfsize(self, other){|a, b| a & b} end
    def | (other) calc_set_maxsize(self, other){|a, b| a | b} end

    def * (times)
      tr = self.class.new
      size = self[-1].offset
      eot, orig = partition{|x| EndOfTrack === x}
      times.times do |i|
	tr += orig.collect{|ev| ev = ev.dup; ev.offset += size * i; ev}
      end
      unless eot.empty?
	x = eot.max
	x.offset += size * times
	tr << x
      end
      tr
    end

    def << (ev)
      @arr << ev
      self
    end

    def >> (ev)
      @arr.reject!{|x| x.object_id == ev.object_id}
      self
    end

    def concat!(other) replace(self + other) end

    def each
      i = 0
      @arr.compact.sort_by{|x| [x, i += 1]}.
	each do |ev|
	yield ev
      end
      self
    end

    def _merge(a, b, &block)
      c = []
      until a.empty? || b.empty?
	c << if yield(a[0], b[0]) <= 0 then a.shift else b.shift end
      end
      c + a + b
    end

    def _sort(xs, &block)
      mid = (xs.size / 2).truncate
      if mid < 1
	xs
      else
	_merge(_sort(xs[0...mid], &block), _sort(xs[mid..-1], &block), &block)
      end
    end

    def _sort_by(xs)
      xs.collect{|x| [x, yield(x)]}.
	sort{|a, b| a[1] <=> b[1]}.
	collect!{|x| x[0]}
    end

    private :_merge, :_sort, :_sort_by

    def sort(&block)
      block ||= proc{|a, b| a <=> b}
      _sort(@arr, &block)
    end

    def sort!(&block)
      replace(sort(&block))
      self
    end

    def sort_by(&block)
      _sort_by(@arr, &block)
    end

    def replace(another)
      @arr.replace(another.to_a)
      self
    end

    extend Forwardable
    include Enumerable

    de = (Array.instance_methods - self.instance_methods)
    de -= %w(assoc flatten flatten! pack rassoc transpose)
    de += %w(include? to_a)

    def_delegators(:@arr, *de)

    undef_method :zip

  end

  class Event

    include Comparable

    def initialize(offset)
      # offset:0/oo
      @offset = offset
    end

    attr_accessor :offset

    def <=>(other) self.offset <=> other.offset end

    def hash() @offset.hash end
    def eql? (other) self == other end

    def == (other)
      self.class == other.class &&
	offset == other.offset
    end

  end

  class MIDIMessage < Event; end

  class ChannelMessage < MIDIMessage

    def initialize(offset, ch)
      # ch:0/2**4-1
      super(offset)
      @ch = ch
    end

    attr_accessor :ch

    alias_method :channel, :ch
    alias_method :channel=, :ch=

    def == (other)
      super && ch == other.ch
    end

  end

  class VoiceMessage < ChannelMessage; end

  class NoteOff < VoiceMessage

    def initialize(offset, ch, note, vel)
      # note:0/2**7-1, vel:0/2**7-1
      super(offset, ch)
      @note, @vel = note, vel
    end

    attr_accessor :note

    def vel() @vel || 64 end
    def vel=(vel) @vel = vel end
    def fake?() @vel.nil? end

    alias_method :velocity, :vel
    alias_method :velocity=, :vel=

    def == (other)
      super && note == other.note && vel == other.vel
    end

  end

  class NoteOn < VoiceMessage

    def initialize(offset, ch, note, vel)
      # note:0/2**7-1, vel:0/2**7-1
      super(offset, ch)
      @note, @vel = note, vel
    end

    attr_accessor :note, :vel

    alias_method :velocity, :vel
    alias_method :velocity=, :vel=

    def == (other)
      super && note == other.note && vel == other.vel
    end

  end

  class PolyphonicKeyPressure < VoiceMessage

    def initialize(offset, ch, note, val)
      # note:0/2**7-1, val:0/2**7-1
      super(offset, ch)
      @note, @val = note, val
    end

    attr_accessor :note, :val

    alias_method :value, :val
    alias_method :value=, :val=

    def == (other)
      super && note == other.note && val == other.val
    end

  end

  class ControlChange < VoiceMessage

    def initialize(offset, ch, num, val)
      # num:0/119, val:0/2**7-1
      super(offset, ch)
      @num, @val = num, val
    end

    attr_accessor :num, :val

    alias_method :number, :num
    alias_method :number=, :num=

    alias_method :value, :val
    alias_method :value=, :val=

    def == (other)
      super && num == other.num && val == other.val
    end

  end

  class ProgramChange < VoiceMessage

    def initialize(offset, ch, num)
      # num:0/2**7-1
      super(offset, ch)
      @num = num
    end

    attr_accessor :num

    alias_method :number, :num
    alias_method :number=, :num=

    def == (other)
      super && num == other.num
    end

  end

  class ChannelPressure < VoiceMessage

    def initialize(offset, ch, val)
      # val:0/2**7-1
      super(offset, ch)
      @val = val
    end

    attr_accessor :val

    alias_method :value, :val
    alias_method :value=, :val=

    def == (other)
      super && val == other.val
    end

  end

  class PitchBendChange < VoiceMessage

    def initialize(offset, ch, val)
      # val:-2**13/2**13-1
      super(offset, ch)
      @val = val
    end

    attr_accessor :val

    alias_method :value, :val
    alias_method :value=, :val=

    def == (other)
      super && val == other.val
    end

  end

  class ChannelModeMessage < ChannelMessage; end

  [ 'AllSoundOff',
    'ResetAllControllers',
    'AllNotesOff',
    'OmniOff',
    'OmniOn',
    'PolyMode',
  ].each do |name|
    module_eval <<-"end;"
      class #{name} < ChannelModeMessage

	def initialize(offset, ch)
	  super(offset, ch)
	end

      end
    end;
  end

  [ 'LocalControl', # val:0/127
    'MonoMode',     # val:0/16
  ].each do |name|
    module_eval <<-"end;"
      class #{name} < ChannelModeMessage

	def initialize(offset, ch, val)
	  super(offset, ch)
	  @val = val
	end

	attr_accessor :val

	alias_method :value, :val
	alias_method :value=, :val=

	def == (other)
	  super && val == other.val
	end

      end
    end;
  end

  class SystemMessage < Event; end
  class ExclusiveMessage < SystemMessage; end

  class ExclusiveF0 < ExclusiveMessage

    def initialize(offset, data)
      # data.length:0/2**28-1
      super(offset)
      @data = data
    end

    attr_accessor :data

    def == (other)
      super && data == other.data
    end

  end

  class ExclusiveF7 < ExclusiveMessage

    def initialize(offset, data)
      # data.length:0/2**28-1
      super(offset)
      @data = data
    end

    attr_accessor :data

    def == (other)
      super && data == other.data
    end

  end

  class Meta < Event

    def initialize(offset)
      super(offset)
    end

  end

  class SequenceNumber < Meta

    def initialize(offset, num)
      # num:0/2**16-1
      super(offset)
      @num = num
    end

    attr_accessor :num

    alias_method :number, :num
    alias_method :number=, :num=

    def == (other)
      super && num == other.num
    end

  end

  class Text < Meta

    def initialize(offset, text)
      # text.length:0/2**28-1
      super(offset)
      @text = text
    end

    attr_accessor :text

    def == (other)
      super && text == other.text
    end

  end

  %w(GeneralPurposeText CopyrightNotice TrackName
     InstrumentName Lyric Marker CuePoint ProgramName
     DeviceName Text0A Text0B Text0C Text0D Text0E Text0F).
    each do |name|
    module_eval <<-"end;"
      class #{name} < Text

	def initialize(offset, text)
	  super(offset, text)
	end

      end
    end;
  end

  Text01 = GeneralPurposeText
  Text02 = CopyrightNotice
  SequenceName = TrackName
  Text03 = TrackName
  Text04 = InstrumentName
  Display = Lyric
  Text05 = Lyric
  Text06 = Marker
  Text07 = CuePoint
  Text08 = ProgramName
  Text09 = DeviceName

  class ChannelPrefix < Meta

    def initialize(offset, ch)
      # ch:0/2**4-1
      super(offset)
      @ch = ch
    end

    attr_accessor :ch

    alias_method :channel, :ch
    alias_method :channel=, :ch=

    def == (other)
      super && ch == other.ch
    end

  end

  class MIDIPort < Meta

    def initialize(offset, num)
      # num:0/2**8-1
      super(offset)
      @num = num
    end

    attr_accessor :num

    alias_method :number, :num
    alias_method :number=, :num=

    def == (other)
      super && num == other.num
    end

  end

  class EndOfTrack < Meta

    def initialize(offset)
      super(offset)
    end

  end

  class SetTempo < Meta

    def initialize(offset, tempo)
      # tempo:1/2**24-1
      super(offset)
      @tempo = tempo
    end

    attr_accessor :tempo

    def == (other)
      super && tempo == other.tempo
    end

  end

  class SMPTEOffset < Meta

    def initialize(offset, hr, mn, se, fr, ff, tc)
      # hr:0/23, mn:0/59, se:0/59, fr:0/29, ff:0/99, tc:24|25|29|30
      super(offset)
      @hr, @mn, @se, @fr, @ff, @tc = hr, mn, se, fr, ff, tc
    end

    attr_accessor :hr, :mn, :se, :fr, :ff, :tc

    def == (other)
      super &&
	hr == other.hr &&
	mn == other.mn &&
	se == other.se &&
	fr == other.fr &&
	ff == other.ff &&
	tc == other.tc
    end

  end

  class TimeSignature < Meta

    def initialize(offset, nn, dd, cc, bb)
      # nn,cc,bb:1/2**8-1, dd:0/2**8-1
      super(offset)
      @nn, @dd, @cc, @bb = nn, dd, cc, bb
    end

    attr_accessor :nn, :dd, :cc, :bb

    def == (other)
      super &&
	nn == other.nn &&
	dd == other.dd &&
	cc == other.cc &&
	bb == other.bb
    end

  end

  class KeySignature < Meta

    def initialize(offset, sf, mi)
      # sf:-2**7/2**7-1, mi:0/1
      super(offset)
      @sf, @mi = sf, mi
    end

    attr_accessor :sf, :mi

    def == (other)
      super && sf == other.sf && mi == other.mi
    end
  end

  class SequencerSpecific < Meta

    def initialize(offset, data)
      # data.length:0/2**28-1
      super(offset)
      @data = data
    end

    attr_accessor :data

    def == (other)
      super && data == other.data
    end

  end

end
