#! /usr/bin/env ruby

# play-oss3.rb: Written by Tadayoshi Funaba 1999-2006
# $Id: play-oss3.rb,v 1.4 2006-11-10 21:57:06+09 tadf Exp $

require 'smf'
require 'smf/toy/tempomap'
require 'gopt'
include  SMF

module SMF

  class Sequence

    class Timer

      def initialize() @start = Time.now end
      def elapse() Time.now - @start end

    end

    class Play < XSCallback

      def initialize(tm, num) @tm, @num = tm, num end

      def header(format, ntrks, division, tc)
	@mo = open("/dev/midi%02d" % @num, 'w')
      end

      def track_start() @offset = 0 end

      def delta(delta)
	@timer ||= Timer.new
	if delta.nonzero?
	  @offset += delta
	  e = @tm.offset2elapse(@offset) - @timer.elapse
	  if e > 0
	    sleep(e.to_f)
	  end
	end
      end

      def noteoff(ch, note, vel)
	@mo.putc(ch | 0x80)
	@mo.putc(note)
	@mo.putc(vel)
	@mo.flush
      end

      def noteon(ch, note, vel)
	@mo.putc(ch | 0x90)
	@mo.putc(note)
	@mo.putc(vel)
	@mo.flush
      end

      def polyphonickeypressure(ch, note, val)
	@mo.putc(ch | 0xa0)
	@mo.putc(note)
	@mo.putc(val)
	@mo.flush
      end

      def controlchange(ch, num, val)
	@mo.putc(ch | 0xb0)
	@mo.putc(num)
	@mo.putc(val)
	@mo.flush
      end

      def programchange(ch, num)
	@mo.putc(ch | 0xc0)
	@mo.putc(num)
	@mo.flush
      end

      def channelpressure(ch, val)
	@mo.putc(ch | 0xd0)
	@mo.putc(val)
	@mo.flush
      end

      def pitchbendchange(ch, val)
	@mo.putc(ch | 0xe0)
	val += 0x2000
	lsb =  val       & 0x7f
	msb = (val >> 7) & 0x7f
	@mo.putc(lsb)
	@mo.putc(msb)
	@mo.flush
      end

      def channelmodemessage(ch, num, val) controlchange(ch, num, val) end

      private :channelmodemessage

      def allsoundoff(ch) channelmodemessage(ch, 0x78, 0) end
      def resetallcontrollers(ch) channelmodemessage(ch, 0x79, 0) end
      def localcontrol(ch, val) channelmodemessage(ch, 0x7a, val) end
      def allnotesoff(ch) channelmodemessage(ch, 0x7b, 0) end
      def omnioff(ch) channelmodemessage(ch, 0x7c, 0) end
      def omnion(ch) channelmodemessage(ch, 0x7d, 0) end
      def monomode(ch, val) channelmodemessage(ch, 0x7e, val) end
      def polymode(ch) channelmodemessage(ch, 0x7f, 0) end

      def exclusivefx(data)
	data.each_byte do |x|
	  @mo.putc(x)
	end
	@mo.flush
      end

      private :exclusivefx

      def exclusivef0(data)
	@mo.putc(0xf0)
	exclusivefx(data)
	@mo.flush
      end

      def exclusivef7(data) exclusivefx(data) end

      def result() @mo.close end

    end

    def play(num=0)
      j = join
      tm = TempoMap.new(j)
      WS.new(j, Play.new(tm, num)).read
    end

  end

end

def usage
  warn 'usage: play-oss3 [-d num] [input]'
  exit 1
end

usage unless opt = Gopt.gopt('d:')
usage unless $*.size >= 0 && $*.size <= 1
file = $*.shift
file = nil if file == '-'

num = (opt[:d] || '0').to_i

sq = unless file then Sequence.read($stdin) else Sequence.load(file) end
sq.play(num)
