=begin
	$Id: flow_objects.rb,v 1.2 2006-03-15 04:37:28 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004,2005 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
=end

module GridFlow

#-------- fClasses for: control + misc

# a dummy class that gives access to any stuff global to GridFlow.
FObject.subclass("gridflow",1,1) {
	def _0_profiler_reset
		GridFlow.fobjects.each {|o,*| o.total_time = 0 }
		GridFlow.profiler_reset2 if GridFlow.respond_to? :profiler_reset2
	end
	def _0_profiler_dump
		ol = []
		total=0
		post "-"*32
		post "microseconds percent pointer  constructor"
		GridFlow.fobjects.each {|o,*| ol.push o }

		# HACK: BitPacking is not a real fobject
		# !@#$ is this still necessary?
		ol.delete_if {|o| not o.respond_to? :total_time }

		ol.sort! {|a,b| a.total_time <=> b.total_time }
		ol.each {|o| total += o.total_time }
		total=1 if total<1
		total_us = 0
		ol.each {|o|
			ppm = o.total_time * 1000000 / total
			us = (o.total_time*1E6/GridFlow.cpu_hertz).to_i
			total_us += us
			post "%12d %2d.%04d %08x %s", us,
				ppm/10000, ppm%10000, o.object_id, o.args
		}
		post "-"*32
		post "sum of accounted microseconds: #{total_us}"
		if GridFlow.respond_to? :memcpy_calls then
			post "memcpy calls: #{GridFlow.memcpy_calls} "+
				"; bytes: #{GridFlow.memcpy_bytes}"+
				"; time: #{GridFlow.memcpy_time}"
		end
		if GridFlow.respond_to? :malloc_calls then
			post "malloc calls: #{GridFlow.malloc_calls} "+
				"; bytes: #{GridFlow.malloc_bytes}"+
				"; time: #{GridFlow.malloc_time}"
		end
		post "-"*32
	end
	def _0_formats
		post "-"*32
		GridFlow.fclasses.each {|k,v|
			next if not /#io:/ =~ k
			modes = case v.flags
			when 2; "#out"
			when 4; "#in"
			when 6; "#in/#out"
			end
			post "%s %s: %s", modes, k, v.description
			if v.respond_to? :info then
				post "-> %s", v.info
			end
		}
		post "-"*32
	end
	# security issue if patches shouldn't be allowed to do anything they want
	def _0_eval(*l)
		s = l.map{|x|x.to_i.chr}.join""
		post "ruby: %s", s
		post "returns: %s", eval(s).inspect
	end
	add_creator "@global"
	GridFlow.bind "gridflow", "gridflow" rescue Exception
}
FObject.subclass("fps",1,1) {
	def initialize(*options)
		super
		@history = []   # list of delays between incoming messages
		@last = 0.0     # when was last time
		@duration = 0.0 # how much delay since last summary
		@period = 1     # minimum delay between summaries
		@detailed = false
		@mode = :real
		options.each {|o|
			case o
			when :detailed; @detailed=true
			when :real,:user,:system,:cpu; @mode=o
			end
		}
		def @history.moment(n=1)
			sum = 0
			each {|x| sum += x**n }
			sum/length
		end
	end
	def method_missing(*a) end # ignore non-bangs
	def _0_period x; @period=x end
	def publish
		@history.sort!
		n=@history.length
		fps = @history.length/@duration
		if not @detailed then send_out 0, fps; return end
		send_out 0, fps,
			1000*@history.min,
			500*(@history[n/2]+@history[(n-1)/2]),
			1000*@history.max,
			1000/fps,
			1000*(@history.moment(2) - @history.moment(1)**2)**0.5
	end
	def _0_bang
		t = case @mode
		when :real; Time.new.to_f
		when :user; Process.times.utime
		when :system; Process.times.stime
		when :cpu; GridFlow.rdtsc/GridFlow.cpu_hertz
		end
		@history.push t-@last
		@duration += t-@last
		@last = t
		return if @duration<@period
		fps = @history.length/@duration
		publish if fps>0.001
		@history.clear
		@duration = 0
	end
}

# to see what the messages look like when they get on the Ruby side.
FObject.subclass("rubyprint",1,0) {
	def initialize(*a)
		super
		@time = !!(a.length and a[0]==:time)
	end

	def method_missing(s,*a)
		s=s.to_s
		pre = if @time then sprintf "%10.6f  ", Time.new.to_f else "" end
		case s
		when /^_0_/; post "%s","#{pre}#{s[3..-1]}: #{a.inspect}"
		else super
		end
	end
}
FObject.subclass("printargs",0,0) {
	def initialize(*a) super; post a.inspect end
}
GridObject.subclass("#print",1,0) {
	install_rgrid 0, true
	attr_accessor :name
	def initialize(name=nil)
		super # don't forget super!!!
		if name then @name = name.to_s+": " else @name="" end
		@base=10; @format="d"; @trunc=70; @maxrows=50
	end
	def end_hook; end # other hijackability
	def format
		case @nt
		when :float32; '%6.6f'
		when :float64; '%14.14f'
		else "%#{@columns}#{@format}" end
	end
	def _0_base(x)
		@format = (case x
		when 2; "b"
		when 8; "o"
		when 10; "d"
		when 16; "x"
		else raise "base #{x} not supported" end)
		@base = x
	end
	def _0_trunc(x)
		x=x.to_f
		(0..240)===x or raise "out of range (not in 0..240 range)"
		@trunc = x
	end
	def _0_maxrows(x) @maxrows = x.to_i end
	def make_columns udata
		min = udata.min
		max = udata.max
		@columns = "" # huh?
		@columns = [
			sprintf(format,min).length,
			sprintf(format,max).length].max
	end
	def unpack data
		ps = GridFlow.packstring_for_nt @nt
		data.unpack ps
	end
	def _0_rgrid_begin
		@dim = inlet_dim 0
		@nt = inlet_nt 0
		@data = ""
	end
	def _0_rgrid_flow(data) @data << data end
	def _0_rgrid_end
		head = "#{name}Dim[#{@dim.join','}]"
		head << "(#{@nt})" if @nt!=:int32
		head << ": "
		if @dim.length > 3 then
			post head+" (not printed)"
		elsif @dim.length < 2 then
			udata = unpack @data
			make_columns udata
			post trunc(head + dump(udata))
		elsif @dim.length == 2 then
			post head
			udata = unpack @data
			make_columns udata
			sz = udata.length/@dim[0]
			rown = 1
			for row in 0...@dim[0] do
				post trunc(dump(udata[sz*row,sz]))
				rown += 1
				(post "..."; break) if rown>@maxrows
			end
		elsif @dim.length == 3 then
			post head
			make_columns unpack(@data)
			sz = @data.length/@dim[0]
			sz2 = sz/@dim[1]
			rown = 1
			for row in 0...@dim[0]
				column=0; str=""
				for col in 0...@dim[1]
					str << "(" << dump(unpack(@data[sz*row+sz2*col,sz2])) << ")"
					break if str.length>@trunc
				end
				post trunc(str)
				rown += 1
				(post "..."; break) if rown>@maxrows
			end
		end
		@data,@dim,@nt = nil
		end_hook
	end
	def dump(udata,sep=" ")
		f = format
		udata.map{|x| sprintf f,x }.join sep
	end
	def trunc s
		if s.length>@trunc then s[0...@trunc]+" [...]" else s end
	end
}
GridPack =
GridObject.subclass("#pack",1,1) {
	install_rgrid 0
	class<<self;attr_reader :ninlets;end
	def initialize(n=2,cast=:int32)
		n||=self.class.ninlets
		n>=16 and raise "too many inlets"
		super
		@data=[0]*n
		@cast=cast
		@ps  =GridFlow.packstring_for_nt cast
	end
	def initialize2
		return if self.class.ninlets>1
		add_inlets @data.length-1
	end
	def _0_cast(cast)
		@ps   = GridFlow.packstring_for_nt cast
		@cast = cast
	end
	def self.define_inlet i
		module_eval "
			def _#{i}_int   x; @data[#{i}]=x; _0_bang; end
			def _#{i}_float x; @data[#{i}]=x; _0_bang; end
		"
	end
	(0...15).each {|x| define_inlet x }
	def _0_bang
		send_out_grid_begin 0, [@data.length], @cast
		send_out_grid_flow 0, @data.pack(@ps), @cast
	end
	self
}

# the install_rgrids in the following are hacks so that
# outlets can work. (install_rgrid is supposed to be for receiving)
# maybe GF-0.8 doesn't need that.
GridPack.subclass("@two",  2,1) { install_rgrid 0; def initialize() super 2 end }
GridPack.subclass("@three",3,1) { install_rgrid 0; def initialize() super 2 end  }
GridPack.subclass("@four", 4,1) { install_rgrid 0; def initialize() super 2 end  }
GridPack.subclass("@eight",8,1) { install_rgrid 0; def initialize() super 2 end  }
GridObject.subclass("#unpack",1,0) {
  install_rgrid 0, true
  def initialize(n=2)
    @n=n
    n>=10 and raise "too many outlets"
    super
  end
  def initialize2; add_outlets @n end
  def _0_rgrid_begin
    inlet_dim(0)==[@n] or raise "expecting Dim[#{@n}], got Dim#{@dim}"
    inlet_set_factor 0,@n
  end
  def _0_rgrid_flow data
    @ps = GridFlow.packstring_for_nt inlet_nt(0)
    duh = data.unpack(@ps)
    i=duh.size-1
    until i<0 do send_out i,duh[i]; i-=1 end
  end
  def _0_rgrid_end; end
}

GridObject.subclass("#export_symbol",1,1) {
	install_rgrid 0
	def _0_rgrid_begin; @data="" end
	def _0_rgrid_flow data; @data << data; end
	def _0_rgrid_end
		send_out 0, :symbol, @data.unpack("I*").pack("c*").intern
	end
}
GridObject.subclass("unix_time",1,3) {
  install_rgrid 0
  def _0_bang
    t = Time.new
    tt = t.to_s
    send_out_grid_begin 0, [tt.length], :uint8
    send_out_grid_flow 0, tt, :uint8
    send_out 1, t.to_i/86400, t.to_i%86400,
	((t.to_f-t.to_f.floor)*1000000).to_i
    send_out 2, t.year, t.month, t.day, t.hour, t.min, t.day
  end
}
### test with "shell xlogo &" -> [exec]
FObject.subclass("exec",1,0) {
        def _0_shell(*a) system(a.map!{|x| x.to_s }.join(" ")) end
}
FObject.subclass("renamefile",1,0) {
        def initialize; end
        def _0_list(a,b) File.rename(a.to_s,b.to_s) end
}
FObject.subclass("ls",1,1) {
        def _0_symbol(s) send_out 0, :list, *Dir.new(s.to_s).map {|x| x.intern } end
        def _0_glob  (s) send_out 0, :list, *Dir[    s.to_s].map {|x| x.intern } end
}

#-------- fClasses for: math

FPatcher.subclass("gfmessagebox",1,1) {
  def initialize(*a) @a=a end
  def _0_float(x)  send_out 0, *@a.map {|y| if y=="$1".intern then x else y end } end
  def _0_symbol(x) send_out 0, *@a.map {|y| if y=="$1".intern then x else y end } end
}

FPatcher.subclass("@!",1,1) {
  @fobjects = ["# +","#type","gfmessagebox list $1 #"]
  @wires = [-1,0,1,0, 1,0,2,0, 2,0,0,1, -1,0,0,0, 0,0,-1,0]
  def initialize(sym)
	super
	@fobjects[0].send_in 0, case sym
		when :rand; "op rand"; when :sqrt; "op sqrt"
		when :abs;  "op abs-"; when :sq;   "op sq-"
		else raise "bork BORK bork" end
  end
}
FPatcher.subclass("@fold",2,1) {
  @fobjects = ["#fold +","gfmessagebox seed $1"]
  @wires = [-1,0,0,0, -1,1,1,0, 1,0,0,1, 0,0,-1,0]
  def initialize(op,seed=0) super; o=@fobjects[0]
	o.send_in 0, :op, op; o.send_in 0, :seed, seed end
}
FPatcher.subclass("@scan",2,1) {
  @fobjects = ["#scan +","gfmessagebox seed $1"]
  @wires = [-1,0,0,0, -1,1,1,0, 1,0,0,1, 0,0,-1,0]
  def initialize(op,seed=0) super; o=@fobjects[0]
	o.send_in 0, :op, op; o.send_in 0, :seed, seed end
}
FPatcher.subclass("@inner",3,1) {
  @fobjects = ["#inner","gfmessagebox seed $1"]
  @wires = [-1,0,0,0, -1,1,1,0, 1,0,0,0, 0,0,-1,0, -1,2,0,1]
  def initialize(op=:*,fold=:+,seed=0,r=0) super; o=@fobjects[0]
	o.send_in 0, :op, op; o.send_in 0, :fold, fold
	o.send_in 0, :seed, seed; o.send_in 1, r end
}
FPatcher.subclass("@convolve",2,1) {
  @fobjects = ["#convolve"]
  @wires = [-1,0,0,0, -1,2,0,1, 0,0,-1,0]
  def initialize(op=:*,fold=:+,seed=0,r=0) super; o=@fobjects[0]
	o.send_in 0, :op, op; o.send_in 0, :fold, fold
	o.send_in 0, :seed, seed; o.send_in 1, r end
}

#-------- fClasses for: video

FPatcher.subclass("@scale_to",2,1) {
	@fobjects = [
		"@for {0 0} {42 42} {1 1}","@ *","@ /",
		"@store","#dim","@redim {2}","#finished",
	]
	@wires = []
	for i in 1..3 do @wires.concat [i-1,0,i,0] end
	@wires.concat [3,0,-1,0, 4,0,5,0, 5,0,1,1, 6,0,0,0,
		-1,0,4,0, -1,0,3,1, -1,0,6,0, -1,1,0,1, -1,1,2,1]
	def initialize(size)
		(size.length==2 and Numeric===size[0] and Numeric===size[1]) or
			raise "expecting {height width}"
		super
		send_in 1, size
	end
}

#<vektor> told me to:
# RGBtoYUV : @fobjects = ["#inner (3 3 # 66 -38 112 128 -74 -94 25 112 -18)",
#	"@ >> 8","@ + {16 128 128}"]
# YUVtoRGB : @fobjects = ["@ - (16 128 128)",
#	"#inner (3 3 # 298 298 298 0 -100 516 409 -208 0)","@ >> 8"]

FPatcher.subclass("#rotate",2,1) {
	@fobjects = ["#inner","# >> 8"]
	@wires = [-1,0,0,0, 0,0,1,0, 1,0,-1,0]
	def update_rotator
		n = @axis[2]
		rotator = (0...n).map {|i| (0...n).map {|j| if i==j then 256 else 0 end }}
		th = @angle * Math::PI / 18000
		scale = 1<<8
		(0...2).each {|i| (0...2).each {|j|
				a = @axis[i].to_i
				b = @axis[j].to_i
				#GridFlow.post "(#{a},#{b}) #{rotator[a].inspect}"
				rotator[a][b] = (scale*Math.cos(th+(j-i)*Math::PI/2)).to_i
		}}
		@fobjects[0].send_in 1,n,n,"#".intern,*rotator.flatten
	end
	def _0_axis(from,to,total)
		total>=0 or raise "total-axis number incorrect"
		from>=0 and from<total or raise "from-axis number incorrect"
		to  >=0 and to  <total or raise   "to-axis number incorrect"
		@axis = [from.to_i,to.to_i,total.to_i]
		update_rotator
	end
	def initialize(rot=0,axis=[0,1,2])
		super
		@angle=0
		_0_axis(*axis)
		send_in 1, rot
	end
	def _1_int(angle) @angle = angle; update_rotator end
	alias _1_float _1_int
}

FObject.subclass("foreach",1,1) {
	def initialize() super end
	def _0_list(*a)
		a.each {|e|
			if Symbol===e then
				send_out 0,:symbol,e
			else
				send_out 0,e
			end
		}
	end
}
FObject.subclass("listflatten",1,1) {
	def initialize() super end
	def _0_list(*a) send_out 0,:list,*a.flatten end
}
FObject.subclass("rubysprintf",2,1) {
  def initialize(*format) _1_list(format) end
  def _0_list(*a) send_out 0, :symbol, (sprintf @format, *a).intern end
  alias _0_float _0_list
  alias _0_symbol _0_list
  def _1_list(*format) @format = format.join(" ") end
  alias _1_symbol _1_list
}

#-------- fClasses for: jMax compatibility

class JMaxUDPSend < FObject
	def initialize(host,port)
		super
		@socket = UDPSocket.new
		@host,@port = host.to_s,port.to_i
	end
	def encode(x)
		case x
		when Integer; "\x03" + [x].pack("N")
		when Float; "\x04" + [x].pack("g")
		when Symbol, String; "\x01" + x.to_s + "\x02"
		end
	end
	def method_missing(sel,*args)
		sel=sel.to_s.sub(/^_\d_/, "")
		@socket.send encode(sel) +
			args.map{|arg| encode(arg) }.join("") + "\x0b",
			0, @host, @port
	end
	def delete; @socket.close end
	install "jmax_udpsend", 1, 0
end

class JMaxUDPReceive < FObject
	def initialize(port)
		super
		@socket = UDPSocket.new
		@port = port.to_i
		@socket.bind nil, @port
		@clock = Clock.new self
		@clock.delay 0
	end
	def decode s
		n = s.length
		i=0
		m = []
		case s[i]
		when 3; i+=5; m << s[i-4,4].unpack("N")[0]
		when 4; i+=5; m << s[i-4,4].unpack("g")[0]
		when 1; i2=s.index("\x02",i); m << s[i+1..i2-1].intern; i=i2+1
		when 11; break
		else raise "unknown code in udp packet"
		end while i<n
		m
	end
	def call
		ready_to_read = IO.select [@socket],[],[],0
		return if not ready_to_read
		data,sender = @socket.recvfrom 1024
		return if not data
		send_out 1, sender.map {|x| x=x.intern if String===x; x }
		send_out 0, *(decode data)
		@clock.delay 50
	end
	def delete; @clock.unset; @socket.close end
	install "jmax_udpreceive", 0, 2
end

class JMax4UDPSend < FObject
	def initialize(host,port)
		super
		@socket = UDPSocket.new
		@host,@port = host.to_s,port.to_i
		@symbols = {}
	end
	def encode(x)
		case x
		when Integer; "\x01" + [x].pack("N")
		when Float; "\x02" + [x].pack("G")
		when Symbol, String
			x = x.to_s
			y = x.intern
			if not @symbols[y]
				@symbols[y]=true
				"\x04" + [y].pack("N") + x + "\0"
			else
				"\x03" + [y].pack("N")
			end
		end
	end
	def method_missing(sel,*args)
		sel=sel.to_s.sub(/^_\d_/, "")
		sel=(case sel; when "int","float"; ""; else encode(sel) end)
		args=args.map{|arg| encode(arg) }.join("")
		@socket.send(sel+args+"\x0f", 0, @host, @port)
	end
	def delete; @socket.close end
	install "jmax4_udpsend", 1, 0
end

class JMax4UDPReceive < FObject
	def initialize(port)
		super
		@socket = UDPSocket.new
		@port = port.to_i
		@socket.bind nil, @port
		@clock = Clock.new self
		@clock.delay 0
		@symbols = {}
	end
	def decode s
		n = s.length
		i=0
		m = []
		case s[i]
		when 1; i+=5; m << s[i-4,4].unpack("N")[0]
		when 2; i+=9; m << s[i-8,8].unpack("G")[0]
		when 3
			i+=5; sid = s[i-4,4].unpack("N")[0]
			m << @symbols[sid]
		when 4
			i+=5; sid = s[i-4,4].unpack("N")[0]
			i2=s.index("\x00",i)
			@symbols[sid] = s[i..i2-1].intern
			m << @symbols[sid]
			i=i2+1
		when 15; break
		else post "unknown code %d in udp packet %s", s[i], s.inspect; return m
		end while i<n
		m
	end
	def call
		ready_to_read = IO.select [@socket],[],[],0
		return if not ready_to_read
		data,sender = @socket.recvfrom 1024
		return if not data
		send_out 1, sender.map {|x| x=x.intern if String===x; x }
		send_out 0, *(decode data)
		@clock.delay 50
	end
	def delete; @clock.unset; @socket.close end
	install "jmax4_udpreceive", 0, 2
end

class PDNetSocket < FObject
	def initialize(host,port,protocol=:udp,*options)
		super
		_1_connect(host,port,protocol)
		@options = {}
		options.each {|k|
			k=k.intern if String===k
			@options[k]=true
		}
	end
	def _1_connect(host,port,protocol=:udp)
		host = host.to_s
		port = port.to_i
		@host,@port,@protocol = host.to_s,port.to_i,protocol
		case protocol
		when :udp
			@socket = UDPSocket.new
			if host=="-" then
				@socket.bind nil, port
			end
		when :tcp
 			if host=="-" then
				@server = TCPServer.new("localhost",port)
			else
				@socket = TCPSocket.new(host,port)
			end
			
		end
		@clock = Clock.new self
		@clock.delay 0
		@data = ""
	end
	def encode(x)
		x=x.to_i if @options[:nofloat] and Float===x
		x.to_s
	end
	def method_missing(sel,*args)
		sel=sel.to_s
		sel.sub!(/^_\d_/, "") or return super
		sel=(case sel; when "int","float"; ""; else encode(sel) end)
		msg = [sel,*args.map{|arg| encode(arg) }].join(" ")
		if @options[:nosemicolon] then msg << "\n" else msg << ";\n" end
		post "encoding as: %s", msg.inspect if @options[:debug]
		case @protocol
		when :udp; @socket.send msg, 0, @host, @port
		when :tcp; @socket.send msg, 0
		end
	end
	def delete; @clock.unset; @socket.close end
	def decode s
		post "decoding from: %s", s.inspect if @options[:debug]
		s.chomp!("\n")
		s.chomp!("\r")
		s.chomp!(";")
		a=s.split(/[\s\0]+/)
		a.shift if a[0]==""
		a.map {|x|
			case x
			when /-?\d+$/; x.to_i
			when /-?\d/; x.to_f
			else x.intern
			end
		}
	end
	def call
		ready_to_accept = IO.select [@server],[],[],0 if @server
		if ready_to_accept
			@socket.close if @socket
			@socket = @server.accept
		end
		ready_to_read = IO.select [@socket],[],[],0 if @socket
		return if not ready_to_read
		case @protocol
		when :udp
			data,sender = @socket.recvfrom 1024
			send_out 1, sender.map {|x| x=x.intern if String===x; x }
			send_out 0, *(decode data)
		when :tcp
			@data << @socket.sysread(1024)
			sender = @socket.peeraddr
			loop do
				n = /\n/ =~ @data
				break if not n
				send_out 1, sender.map {|x| x=x.intern if String===x; x }
				send_out 0, *(decode @data.slice!(0..n))
			end
		end
		@clock.delay 50
	end
	install "pd_netsocket", 2, 2
end

PDNetSocket.subclass("pd_netsend",1,0) {}
PDNetSocket.subclass("pd_netreceive",0,2) {
	def initialize(port) super("-",port) end
}

# bogus class for representing objects that have no recognized class.
FObject.subclass("broken",0,0) {
 	def args; a=@args.dup; a[7,0] = " "+classname; a end
}

FObject.subclass("fork",1,2) {
  def method_missing(sel,*args)
    sel.to_s =~ /^_(\d)_(.*)$/ or super
	send_out 1,$2.intern,*args
	send_out 0,$2.intern,*args
  end
}
FObject.subclass("shunt",2,0) {
	def initialize(n=2,i=0) super; @n=n; @i=i end
	def initialize2; add_outlets @n end
	def method_missing(sel,*args)
		sel.to_s =~ /^_(\d)_(.*)$/ or super
		send_out @i,$2.intern,*args
	end
	def _1_int i; @i=i.to_i % @n end
	alias :_1_float :_1_int
	# hack: this is an alias.
	class Demux < self; install "demux", 2, 0; end
}

#-------- fClasses for: jmax2pd

	FObject.subclass("button",1,1) {
		def method_missing(*) send_out 0 end
	}
	FObject.subclass("toggle",1,1) {
		def _0_bang; @state ^= true; trigger end
		def _0_int x; @state = x!=0; trigger end
		def trigger; send_out 0, (if @state then 1 else 0 end) end
	}
	FObject.subclass("jpatcher",0,0) {
		def initialize(*a) super; @subobjects={} end
		attr_accessor :subobjects
	}
	FObject.subclass("jcomment",0,0) {}
	FObject.subclass("loadbang",0,1) { def trigger; send_out 0 end }
	FObject.subclass("messbox",1,1) {
		def _0_bang; send_out 0, *@argv end
		def clear; @argv=[]; end
		def append(*argv) @argv<<argv; end
	}

#-------- fClasses for: list manipulation (jMax-compatible)

	FObject.subclass("listmake",2,1) {
		def initialize(*a) @a=a end
		def _0_list(*a) @a=a; _0_bang end
		def _1_list(*a) @a=a end
		def _0_bang; send_out 0, :list, *@a end
	}
	FObject.subclass("listlength",1,1) {
		def initialize() super end
		def _0_list(*a) send_out 0, a.length end
	}
	FObject.subclass("listelement",2,1) {
		def initialize(i=0) super; @i=i.to_i end
		def _1_int(i) @i=i.to_i end; alias _1_float _1_int
		def _0_list(*a)
			e=a[@i]
			if Symbol===e then
				send_out 0, :symbol, e
			else
				send_out 0, e
			end
		end
	}
	FObject.subclass("listsublist",3,1) {
		def initialize(i=0,n=1) super; @i,@n=i.to_i,n.to_i end
		def _1_int(i) @i=i.to_i end; alias _1_float _1_int
		def _2_int(n) @n=n.to_i end; alias _2_float _2_int
		def _0_list(*a) send_out 0, :list, *a[@i,@n] end
	}
	FObject.subclass("listprepend",2,1) {
		def initialize(*b) super; @b=b end
		def _0_list(*a) a[0,0]=@b; send_out 0, :list, *a end
		def _1_list(*b) @b=b end
	}
	FObject.subclass("listappend",2,1) {
		def initialize(*b) super; @b=b end
		def _0_list(*a) a[a.length,0]=@b; send_out 0, :list, *a end
		def _1_list(*b) @b=b end
	}
	FObject.subclass("listreverse",1,1) {
		def initialize() super end
		def _0_list(*a) send_out 0,:list,*a.reverse end
	}
	FObject.subclass("messageprepend",2,1) {
		def initialize(*b) super; @b=b end
		def _0_(*a) a[0,0]=@b; send_out 0, *a end
		def _1_list(*b) @b=b end
		def method_missing(sym,*a)
			(m = /(_\d_)(.*)/.match sym.to_s) or return super
			_0_ m[2].intern, *a
		end
	}
	FObject.subclass("messageappend",2,1) {
		def initialize(*b) super; @b=b end
		def _0_(*a) a[a.length,0]=@b; send_out 0, *a end
		def _1_list(*b) @b=b end
		def method_missing(sym,*a)
			(m = /(_\d_)(.*)/.match sym.to_s) or return super
			_0_ m[2].intern, *a
		end
	}

# this was the original demo for the Ruby/jMax/PureData bridges
# FObjects are Ruby Objects that are exported to the PureData system.
# _0_bang means bang message on inlet 0
# FObject#send_out sends a message through an outlet
FObject.subclass("for",3,1) {
	attr_accessor :start, :stop, :step
	def cast(key,val)
		val = Integer(val) if Float===val
		raise ArgumentError, "#{key} isn't a number" unless Integer===val
	end
	def initialize(start,stop,step)
		super
		cast("start",start)
		cast("stop",stop)
		cast("step",step)
		@start,@stop,@step = start,stop,step
	end
	def _0_bang
		x = start
		if step > 0
			(send_out 0, x; x += step) while x < stop
		elsif step < 0
			(send_out 0, x; x += step) while x > stop
		end
	end
	def _0_float(x) self.start=x; _0_bang end
	alias _1_float stop=
	alias _2_float stop=
}
FObject.subclass("oneshot",2,1) {
	def initialize(state=true) @state=state!=0 end
	def method_missing(sel,*a)
		m = /^_0_(.*)$/.match(sel.to_s) or return super
		send_out 0, m[1].intern, *a if @state
		@state=false
	end
	def _1_int(state) @state=state!=0 end
	alias _1_float _1_int
	def _1_bang; @state=true end
}
FObject.subclass("inv+",2,1) {
  def initialize(b=0) @b=b end; def _1_float(b) @b=b end
  def _0_float(a) send_out 0, :float, @b-a end
}
FObject.subclass("inv*",2,1) {
  def initialize(b=0) @b=b end; def _1_float(b) @b=b end
  def _0_float(a) send_out 0, :float, @b/a end
}  
FObject.subclass("range",1,1) {
  def initialize(*a) @a=a end
  def initialize2
    add_inlets  @a.length
    add_outlets @a.length
  end
  def _0_float(x) i=0; i+=1 until @a[i]==nil or x<@a[i]; send_out i,x end
  def method_missing(sel,*a)
    m = /^(_\d+_)(.*)/.match(sel.to_s) or return super
    m[2]=="float" or return super
    @a[m[1].to_i-1] = a[0]
    post "setting a[#{m[1].to_i-1}] = #{a[0]}"
  end
}
FObject.subclass("listfind",2,1) {
  def initialize(*a) _1_list(*a) end
  def _1_list(*a) @a = a end
  def _0_float(x)
    i=0
    while i<@a.length
      (send_out 0,i; return) if @a[i]==x
      i+=1
    end
    send_out 0,-1
  end
  doc:_1_list,"list to search into"
  doc:_0_float,"float to find in that list"
  doc_out:_0_float,"position of the incoming float in the stored list"
}

#-------- fClasses for: GUI

module Gooey # to be included in any FObject class
	def initialize(*)
		super
		@selected=false
		@bg  = "#ffffff" # white background
		@bgb = "#000000" # black border
		@bgs = "#0000ff" # blue border when selected
		@fg  = "#000000" # black foreground
		@rsym = "#{self.class}#{self.object_id}".intern # unique id for use in Tcl
		@can = nil    # the canvas number
		@canvas = nil # the canvas string
		@y,@x = 0,0 # position on canvas
		@sy,@sx = 16,16 # size on canvas
		@font = "Courier -12"
		@vis = nil
	end
	attr_reader :canvas
	attr_reader :selected
	def canvas=(can)
		@can = can if Integer===can
		@canvas = case can
		  when String; can
		  when Integer; ".x%x.c"%(4*can)
		  else raise "huh?"
		end
	end
	def initialize2(*) GridFlow.bind self, @rsym.to_s end
	def pd_displace(can,x,y) self.canvas||=can; @x+=x; @y+=y; pd_show(can) end
	def pd_activate(can,*) self.canvas||=can end
	def quote(text) # for tcl (isn't completely right ?)
		text=text.gsub(/[\{\}]/) {|x| "\\"+x }
		"{#{text}}"
	end
	def pd_vis(can,vis)
	self.canvas||=can; @vis=vis!=0; update end
	def update; pd_show @can if @vis end
	def pd_getrect(can)
		self.canvas||=can
		@x,@y = get_position(can)
		# the extra one-pixel on each side was for #peephole only
		# not sure what to do with this
		[@x-1,@y-1,@x+@sx+1,@y+@sy+1]
	end
	def pd_click(can,x,y,shift,alt,dbl,doit) return 0 end
	def outline; if selected then @bgs else "#000000" end end
	def pd_select(can,sel)
		self.canvas||=can
		@selected=sel!=0
		GridFlow.gui %{ #{canvas} itemconfigure #{@rsym} -outline #{outline} \n }
	end
	def pd_delete(can) end
	def pd_show(can)
		self.canvas||=can
		@x,@y = get_position can if can
	end
	def highlight(color,ratio) # doesn't use self
		c = /^#(..)(..)(..)/.match(color)[1..3].map {|x| x.hex }
		c.map! {|x| [255,(x*ratio).to_i].min }
		"#%02x%02x%02x" % c
	end
end

class Display < FObject; include Gooey
	attr_accessor :text
	def initialize()
		super
		@sel = nil; @args = [] # contents of last received message
		@text = "..."
		@sy,@sx = 16,80 # default size of the widget
		@bg,@bgs,@fg = "#6774A0","#00ff80","#ffff80"
	end
	def _0_set_size(sy,sx) @sy,@sx=sy,sx end
	def atom_to_s a
		case a
		when Float; sprintf("%.5f",a).gsub(/\.?0+$/, "")
		else a.to_s
		end
	end
	def method_missing(sel,*args)
		m = /^(_\d+_)(.*)/.match(sel.to_s) or return super
		@sel,@args = m[2].intern,args
		@text = case @sel
			when nil; "..."
			when :float; atom_to_s @args[0]
			else @sel.to_s + ": " + @args.map{|a| atom_to_s a }.join(' ')
			end
		update
	end
	def pd_show(can)
		super
		return if not canvas or not @vis # can't show for now...
		GridFlow.gui %{
			set canvas #{canvas}
			$canvas delete #{@rsym}TEXT
			set y #{@y+2}
			foreach line [split #{quote @text} \\n] {
				$canvas create text #{@x+2} $y -fill #{@fg} -font #{quote @font}\
					-text $line -anchor nw -tag #{@rsym}TEXT
				set y [expr $y+14]
			}
			foreach {x1 y1 x2 y2} [$canvas bbox #{@rsym}TEXT] {}
			set sx [expr $x2-$x1+1]
			set sy [expr $y2-$y1+3]
			$canvas delete #{@rsym}
			$canvas create rectangle #{@x} #{@y} \
				[expr #{@x}+$sx] [expr #{@y}+$sy] -fill #{@bg} \
				-tags #{@rsym} -outline #{outline}
			$canvas lower #{@rsym} #{@rsym}TEXT
			pd \"#{@rsym} set_size $sy $sx;\n\";
		}
	end
	def pd_delete(can)
		if @vis
			GridFlow.gui %{ #{canvas} delete #{@rsym} #{@rsym}TEXT \n}
		end
		super
	end
	def delete; super end
	def _0_grid(*foo) # big hack!
		# hijacking a [#print]
		gp = FObject["#print"]
		@text = ""
		overlord = self
		gp.instance_eval { @overlord = overlord }
		def gp.post(fmt,*args) @overlord.text << sprintf(fmt,*args) << "\n" end
		def gp.end_hook
			@overlord.instance_eval{@text.chomp!}
			@overlord.update
		end
		#gp.send_in 0, :trunc, 70
		gp.send_in 0, :maxrows, 20
		gp.send_in 0, :grid, *foo
	end

	install "display", 1, 1
	gui_enable if GridFlow.bridge_name =~ /puredata/
end

class GridEdit < GridObject; include Gooey
	def initialize(grid)
		super
		@store = GridStore.new
		@store.connect 0,self,2
		@fin = GridFinished.new
		@fin.connect 0,self,3
		@bg,@bgs,@fg = "#609068","#0080ff","#ff80ff"
		@bghi = highlight(@bg,1.25) # "#80C891"   # highlighted @bg
		#@bghihi = highlight(@bghi,1.5) # very highlighted @bg
		@cellsy,@cellsx = 16,48
		@i,@j = nil,nil # highlighted cell dex
		send_in 0, grid
	end
	def _0_cell_size(sy,sx) @cellsy,@cellsx=sy,sx; update end
	def _0_float(*a) @store.send_in 1,:float,*a; @store.send_in 0; update end
	def _0_list (*a) @store.send_in 1, :list,*a; @store.send_in 0; update end
	def _0_grid (*a) @store.send_in 1, :grid,*a;   @fin.send_in 0, :grid,*a end
	def _3_bang; @store.send_in 0; update end
	def edit_start(i,j)
		edit_end if @i
		@i,@j=i,j
		GridFlow.gui %{
			set canvas #{canvas}
			$canvas itemconfigure #{@rsym}CELL_#{@i}_#{@j} -fill #{@bghi}
		}
	end
	def edit_end
		GridFlow.gui %{
			set canvas #{canvas}
			$canvas itemconfigure #{@rsym}CELL_#{@i}_#{@j} -fill #{@bg}
		}
		unfocus @can
	end
	def _2_rgrid_begin
		@data = []
		@dim = inlet_dim 2
		@nt = inlet_nt 2
		post "_2_rgrid_begin: dim=#{@dim.inspect} nt=#{@nt.inspect}"
		send_out_grid_begin 0, @dim, @nt
	end
	def _2_rgrid_flow data
		ps = GridFlow.packstring_for_nt @nt
		@data[@data.length,0] = data.unpack(ps)		
		post "_2_rgrid_flow: data=#{@data.inspect}"
		send_out_grid_flow 0, data
	end
	def _2_rgrid_end
		post "_2_rgrid_end"
	end
	def pd_click(can,x,y,shift,alt,dbl,doit)
		post "pd_click: %s", [can,x,y,shift,alt,dbl,doit].inspect
		return 0 if not doit!=0
		i = (y-@y-1)/@cellsy
		j = (x-@x-1)/@cellsx
		post "%d,%d", i,j
		ny = @dim[0] || 1
		nx = @dim[1] || 1
		if (0...ny)===i and (0...nx)===j then
			focus @can,x,y
			edit_start i,j
		end
		return 0
	end
	def pd_key(key)
		post "pd_key: %s", [key].inspect
		if key==0 then unfocus @can; return end
	end
	def pd_motion(dx,dy)
		post "pd_motion: %s", [dx,dy].inspect
		ny = @dim[0] || 1
		nx = @dim[1] || 1
		k = @i*nx+@j
		post "@data[#{k}]=#{@data[k]} before"
		@data[k]-=dy
		@store.send_in 1, :put_at, [@i,@j]
		@store.send_in 1, @data[k]
		@store.send_in 0
		post "@data[#{k}]=#{@data[k]} after"
		update
	end
	def pd_show(can)
		super
		return if not can
		ny = @dim[0] || 1
		nx = @dim[1] || 1
		@sy = 2+@cellsy*ny
		@sx = 2+@cellsx*nx
		g = %{
			set canvas #{canvas}
			$canvas delete #{@rsym} #{@rsym}CELL
			$canvas create rectangle #{@x} #{@y} #{@x+@sx} #{@y+@sy} \
				-fill #{@bg} -tags #{@rsym} -outline #{outline}
		}
		ny.times {|i|
		  nx.times {|j|
		    y1 = @y+1+i*@cellsy; y2 = y1+@cellsy
		    x1 = @x+1+j*@cellsx; x2 = x1+@cellsx
		    v = @data[i*nx+j]
		    g << %{
			$canvas create rectangle #{x1} #{y1} #{x2} #{y2} -fill #{@bg} \
				-tags {#{@rsym}CELL #{@rsym}CELL_#{i}_#{j}} -outline #{outline}
			$canvas create text #{x2-4} #{y1+2} -text "#{v}" -anchor ne -fill #ffffff \
				-tags {#{@rsym}CELL_#{i}_#{j}_T}
		    }
		  }
		}
		GridFlow.gui g
	end
	install "#edit", 2, 1
	install_rgrid 2, true
	gui_enable if GridFlow.bridge_name =~ /puredata/
end

class Peephole < FPatcher; include Gooey
	@fobjects = ["#dim","#export_list","#downscale_by 1 smoothly","#out","#scale_by 1",
	proc{Demux.new(2)}]
	@wires = [-1,0,0,0, 0,0,1,0, -1,0,5,0, 2,0,3,0, 4,0,3,0, 5,0,2,0, 5,1,4,0, 3,0,-1,0]
	def initialize(sy=32,sx=32,*args)
		super
		@fobjects[1].connect 0,self,2
		post "Peephole#initialize: #{sx} #{sy} #{args.inspect}"
		@scale = 1
		@down = false
		@sy,@sx = sy,sx # size of the widget
		@fy,@fx = 0,0   # size of last frame after downscale
		@bg,@bgs = "#A07467","#00ff80"
	end
	def pd_show(can)
		super
		return if not can
		if not @open
			GridFlow.gui %{
				pd \"#{@rsym} open [eval list [winfo id #{@canvas}]] 1;\n\";
			}
			@open=true
		end
		# round-trip to ensure this is done after the open
		GridFlow.gui %{
			pd \"#{@rsym} set_geometry #{@y} #{@x} #{@sy} #{@sx};\n\";
		}
		GridFlow.gui %{
			set canvas #{canvas}
			$canvas delete #{@rsym}
			$canvas create rectangle #{@x} #{@y} #{@x+@sx} #{@y+@sy} \
				-fill #{@bg} -tags #{@rsym} -outline #{outline}
		}
		set_geometry_for_real_now
	end
	def set_geometry_for_real_now
		@fy,@fx=@sy,@sx if @fy<1 or @fx<1
		@down = (@fx>@sx or @fy>@sx)
		if @down then
			@scale = [(@fy+@sy-1)/@sy,(@fx+@sx-1)/@sx].max
			@scale=1 if @scale<1 # what???
			@fobjects[2].send_in 1, @scale
			sy2 = @fy/@scale
			sx2 = @fx/@scale
		else
			@scale = [@sy/@fy,@sx/@fx].min
			@fobjects[4].send_in 1, @scale
			sy2 = @fy*@scale
			sx2 = @fx*@scale
		end
		begin
			@fobjects[5].send_in 1, (if @down then 0 else 1 end)
			x2=@y+(@sy-sy2)/2
			y2=@x+(@sx-sx2)/2
			@fobjects[3].send_in 0, :set_geometry,
				x2, y2, sy2, sx2
		rescue StandardError => e
			post "peeperr: %s", e.inspect
		end
		post "set_geometry_for_real_now (%d,%d) (%d,%d) (%d,%d) (%d,%d) (%d,%d)",
			@x+1,@y+1,@sx,@sy,@fx,@fy,sx2,sy2,x2,y2
	end
	def _0_open(wid,use_subwindow)
		post "%s", [wid,use_subwindow].inspect
		@use_subwindow = use_subwindow==0 ? false : true
		if @use_subwindow then
			@fobjects[3].send_in 0, :open,:x11,:here,:embed_by_id,wid
		end
	end
	def _0_set_geometry(y,x,sy,sx)
		@sy,@sx = sy,sx
		@y,@x = y,x
		set_geometry_for_real_now
	end
	def _0_fall_thru(flag) # never worked ?
		post "fall_thru: #{flag}"
		@fobjects[3].send_in 0, :fall_thru, flag
	end
	# note: the numbering here is a FPatcher gimmick... -1,0 goes to _1_.
	def _1_position(y,x,b)
		s=@scale
		if @down then y*=s;x*=s else y*=s;x*=s end
		send_out 0,:position,y,x,b
	end
	def _2_list(sy,sx,chans)
		@fy,@fx = sy,sx
		set_geometry_for_real_now
	end
	def _0_paint()
		post "paint()"
		@fobjects[3].send_in 0, "draw"
	end
	def delete
		post "deleting peephole"
		GridFlow.gui %{ #{canvas} delete #{@rsym} \n}
		@fobjects[3].send_in 0, :close
		super
	end
	def method_missing(s,*a)
		#post "%s: %s", s.to_s, a.inspect
		super rescue NameError
	end

	install "#peephole", 1, 1
	gui_enable if GridFlow.bridge_name =~ /puredata/
	#GridFlow.addtomenu "#peephole" # was this IMPD-specific ?
end

#-------- fClasses for: Hardware

# requires Ruby 1.8.0 because of bug in Ruby 1.6.x
FObject.subclass("joystick_port",0,1) {
  def initialize(port)
    raise "sorry, requires Ruby 1.8" if RUBY_VERSION<"1.8"
    @f = File.open(port.to_s,"r+")
    @status = nil
    @clock = Clock.new self
    @clock.delay 0
    @f.nonblock=true
  end
  def delete; @clock.unset; @f.close end
  def call
    loop{
      begin
        event = @f.read(8)
      rescue Errno::EAGAIN
	@clock.delay 0
        return
      end
      return if not event
      return if event.length<8
      send_out 0, *event.unpack("IsCC")
    }
  end
}

# plotter control (HPGL)
FObject.subclass("plotter_control",1,1) {
  def puts(x)
    x<<"\n"
    x.each_byte {|b| send_out 0, b }
    send_out 0
  end
  def _0_pu; puts "PU;" end
  def _0_pd; puts "PD;" end
  def _0_pa x,y; puts "PA#{x},#{y};" end
  def _0_sp c; puts "SP#{c};"; end
  def _0_ip(*v) puts "IP#{v.join','};" end
  def _0_other(command,*v) puts "#{command.to_s.upcase}#{v.join','};" end
  def _0_print(*text) puts "LB#{text.join(' ')}\003;" end
  def _0_print_from_ascii(*codes)
    _0_print codes.map{|code| code.chr }.join("")
  end
}

# ASCII, useful for controlling pics
FObject.subclass("ascii",1,1) {
  def puts(x)
  x.each_byte {|b| send_out 0, b }
  end
  def _0_float x; puts "#{x.to_i}" end
}

# System, similar to shell
FObject.subclass("system",1,1) {
  def _0_system(*a)
    system(a.join(" "))
  end
}

(begin require "linux/ParallelPort"; true; rescue LoadError; false end) and
FObject.subclass("parallel_port",1,3) {
  def initialize(port,manually=0)
    @f = File.open(port.to_s,"r+")
    @f.extend Linux::ParallelPort
    @status = nil
    @flags = nil
    @manually = manually!=0
    @clock = (if @manually then nil else Clock.new self end)
    @clock.delay 0 if @clock
  end
  def delete; @clock.unset unless @manually; @f.close end
  def _0_int(x) @f.write x.to_i.chr; @f.flush end
  alias _0_float _0_int
  def call
    flags = @f.port_flags
    send_out 2, flags if @flags != flags
    @flags = flags
    status = @f.port_status
    send_out 1, status if @status != status
    @status = status
    @clock.delay 20 if @clock
  end
  def _0_bang
    @status = @flags = nil
    call
  end
  # outlet 0 reserved (future use)
}

(begin require "linux/SoundMixer"; true; rescue LoadError; false end) and
#FObject.subclass("SoundMixer",1,1) {
class GFSoundMixer < FObject; install "SoundMixer",1,1
  # BUG? i may have the channels (left,right) backwards
  def initialize(filename)
    super
    @file = File.open filename.to_s, 0
    @file.extend Linux::SoundMixer
    $sm = self
  end
  @@vars = Linux::SoundMixer.instance_methods.grep(/=/)
  @@vars_h = {}
  @@vars.each {|attr|
    attr.chop!
    eval %{ def _0_#{attr}(x) @file.#{attr} = x[0]*256+x[1] end }
    @@vars_h[attr]=true
  }
  def _0_get(sel=nil)
    if sel then
      sels=sel.to_s
      sel=sels.intern
      raise if not @@vars_h.include? sel.to_s
      begin
        x = @file.send sel
        send_out 0, sel, "(".intern, (x>>8)&255, x&255, ")".intern
      rescue
        send_out 0, sel, "(".intern, -1, -1, ")".intern
      end
    else
      @@vars.each {|var| _0_get var }
    end
  end
end#}

# experimental
FObject.subclass("rubyarray",2,1) {
  def initialize() @a=[]; @i=0; end
  def _0_float i; @i=i; send_out 0, *@a[@i]; end
  def _1_list(*l) @a[@i]=l; end
  def _0_save(filename,format=nil)
    f=File.open(filename.to_s,"w")
    if format then
      @a.each {|x| f.puts(format.to_s%x) }
    else
      @a.each {|x| f.puts(x.join(",")) }
    end
    f.close
  end
  def _0_load(filename)
    f=File.open(filename.to_s,"r")
    @a.clear
    f.each {|x| @a.push x.split(",").map {|y| Float(y) rescue y.intern }}
    f.close
  end
}

FObject.subclass("regsub",3,1) {
  def initialize(from,to) _1_symbol(from); _2_symbol(to) end
  def _0_symbol(s) send_out 0, :symbol, s.to_s.gsub(@from, @to).intern end
  def _1_symbol(from) @from = Regexp.new(from.to_s.gsub(/`/,"\\")) end
  def _2_symbol(to)   @to = to.to_s.gsub(/`/,"\\") end
  doc:_0_symbol,"a string to transform"
  doc:_1_symbol,"a regexp pattern to be found inside of the string"
  doc:_2_symbol,"a replacement for the found pattern"
  doc_out:_0_symbol,"the transformed string"
}

FObject.subclass("memstat",1,1) {
  def _0_bang
    f = File.open("/proc/#{$$}/stat")
    send_out 0, Float(f.gets.split(" ")[22]) / 1024.0
    f.close
  end
  doc:_0_bang,"lookup process stats for the currently running pd+ruby "+
  	"and figure out how much RAM it uses."
  doc_out:_0_float,"virtual size of RAM in kilobytes (includes swapped out and shared memory)"
}

FObject.subclass("sendgui",1,0) {
  def _0_list(*x)
    GridFlow.gui x.join(" ").gsub(/`/,";")+"\n"
  end
  install "sys_vgui", 1, 0
  doc:_0_list,"a Tcl/Tk command to send to the pd client."
}

end # module GridFlow

begin
  require "gridflow/rblti"
  GridFlow.post "Ruby-LTI support loaded."
rescue Exception => e
  #GridFlow.post "%s", e.inspect
  #GridFlow.post "(rblti not found)"
end