diff options
author | N.N. <matju@users.sourceforge.net> | 2005-10-04 02:02:15 +0000 |
---|---|---|
committer | N.N. <matju@users.sourceforge.net> | 2005-10-04 02:02:15 +0000 |
commit | 5e2a1bc9e56003349e533f7e5841041ba5c04e28 (patch) | |
tree | ad040f6894d9383b732423a74420e732f62a66a5 /externals | |
parent | 520a243c297175386ab31c78c84693a664934a69 (diff) |
starting to commit gridflow 0.8.0 ...
if you know how to use "cvs import" please mail me and i'll use it for 0.8.1
svn path=/trunk/; revision=3646
Diffstat (limited to 'externals')
40 files changed, 14995 insertions, 0 deletions
diff --git a/externals/gridflow/base/bitpacking.c b/externals/gridflow/base/bitpacking.c new file mode 100644 index 00000000..143f6edb --- /dev/null +++ b/externals/gridflow/base/bitpacking.c @@ -0,0 +1,309 @@ +/* + $Id: bitpacking.c,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include "grid.h.fcs" +#include <math.h> +#include <stdlib.h> +#include <stdio.h> + +#define CONVERT1 t = \ + (((in[0] << hb[0]) >> 7) & mask[0]) | \ + (((in[1] << hb[1]) >> 7) & mask[1]) | \ + (((in[2] << hb[2]) >> 7) & mask[2]) + +#define CONVERT2 \ + for (t=0,i=0; i<self->size; i++) t |= (((in[i] << hb[i]) >> 7) & mask[i]); + +#define CONVERT3 \ + for (t=0,i=0; i<self->size; i++) { \ + t |= ((in[i]>>(7-hb[i]))|(in[i]<<(hb[i]-7))) & mask[i]; \ + } + +#define WRITE_LE \ + for (int bytes = self->bytes; bytes; bytes--, t>>=8) *out++ = t; + +#define WRITE_BE { int bytes; \ + bytes = self->bytes; \ + while (bytes--) { out[bytes] = t; t>>=8; }\ + out += self->bytes; } + +/* this macro would be faster if the _increment_ + was done only once every loop. or maybe gcc does it, i dunno */ +#define NTIMES(_x_) \ + for (; n>=4; n-=4) { _x_ _x_ _x_ _x_ } \ + for (; n; n--) { _x_ } + +/* this could be faster (use asm) */ +void swap32 (int n, Pt<uint32> data) { + NTIMES({ + uint32 x = *data; + x = (x<<16) | (x>>16); + x = ((x&0xff00ff)<<8) | ((x>>8)&0xff00ff); + *data++ = x; + }) +} + +/* this could be faster (use asm or do it in int32 chunks) */ +void swap16 (int n, Pt<uint16> data) { + NTIMES({ uint16 x = *data; *data++ = (x<<8) | (x>>8); }) +} + +/* **************************************************************** */ + +template <class T> +static void default_pack(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) { + uint32 t; + int i; + int hb[4]; + uint32 mask[4]; + int sameorder = self->endian==2 || self->endian==::is_le(); + int size = self->size; + + for (i=0; i<self->size; i++) hb[i] = highest_bit(self->mask[i]); + memcpy(mask,self->mask,size*sizeof(uint32)); + + if (sameorder && size==3) { + switch(self->bytes) { + case 2: NTIMES(t=CONVERT1; *((int16 *)out)=t; out+=2; in+=3;) return; + case 4: NTIMES(t=CONVERT1; *((int32 *)out)=t; out+=4; in+=3;) return; + } + } + if (self->is_le()) { + switch (size) { + case 3: for (; n--; in+=3) {CONVERT1; WRITE_LE;} break; + case 4: for (; n--; in+=4) {CONVERT3; WRITE_LE;} break; + default:for (; n--; in+=size) {CONVERT2; WRITE_LE;}} + } else { + switch (size) { + case 3: for (; n--; in+=3) {CONVERT1; WRITE_BE;} break; + case 4: for (; n--; in+=4) {CONVERT3; WRITE_BE;} break; + default:for (; n--; in+=size) {CONVERT2; WRITE_BE;}} + } +} + +#define LOOP_UNPACK(_reader_) \ + for (; n; n--) { \ + int bytes=0; uint32 temp=0; _reader_; \ + for (int i=0; i<self->size; i++, out++) { \ + uint32 t=temp&self->mask[i]; \ + *out = (t<<(7-hb[i]))|(t>>(hb[i]-7)); \ + } \ + } +// *out++ = ((temp & self->mask[i]) << 7) >> hb[i]; + +template <class T> +static void default_unpack(BitPacking *self, int n, Pt<uint8> in, Pt<T> out) { + int hb[4]; + for (int i=0; i<self->size; i++) hb[i] = highest_bit(self->mask[i]); + if (is_le()) { // smallest byte first + LOOP_UNPACK( + for(; self->bytes>bytes; bytes++, in++) temp |= *in<<(8*bytes); + ) + } else { // biggest byte first + LOOP_UNPACK( + bytes=self->bytes; for (; bytes; bytes--, in++) temp=(temp<<8)|*in; + ) + } +} + +/* **************************************************************** */ + +template <class T> +static void pack2_565(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) { + const int hb[3] = {15,10,4}; + const uint32 mask[3] = {0x0000f800,0x000007e0,0x0000001f}; + uint32 t; + NTIMES( t=CONVERT1; *((short *)out)=t; out+=2; in+=3; ) +} + +template <class T> +static void pack3_888(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) { + Pt<int32> o32 = (Pt<int32>)out; + while (n>=4) { + o32[0] = (in[5]<<24) | (in[ 0]<<16) | (in[ 1]<<8) | in[2]; + o32[1] = (in[7]<<24) | (in[ 8]<<16) | (in[ 3]<<8) | in[4]; + o32[2] = (in[9]<<24) | (in[10]<<16) | (in[11]<<8) | in[6]; + o32+=3; in+=12; + n-=4; + } + out = (Pt<uint8>)o32; + NTIMES( out[2]=in[0]; out[1]=in[1]; out[0]=in[2]; out+=3; in+=3; ) +} + +/* +template <> +static void pack3_888(BitPacking *self, int n, Pt<uint8> in, Pt<uint8> out) { + Pt<uint32> o32 = Pt<uint32>((uint32 *)out.p,n*3/4); + Pt<uint32> i32 = Pt<uint32>((uint32 *)in.p,n*3/4); + while (n>=4) { +#define Z(w,i) ((word##w>>(i*8))&255) + uint32 word0 = i32[0]; + uint32 word1 = i32[1]; + uint32 word2 = i32[2]; + o32[0] = (Z(1,1)<<24) | (Z(0,0)<<16) | (Z(0,1)<<8) | Z(0,2); + o32[1] = (Z(1,3)<<24) | (Z(2,0)<<16) | (Z(0,3)<<8) | Z(1,0); + o32[2] = (Z(2,1)<<24) | (Z(2,2)<<16) | (Z(2,3)<<8) | Z(1,2); + o32+=3; i32+=3; + n-=4; + } +#undef Z + out = (Pt<uint8>)o32; + in = (Pt<uint8>)i32; + NTIMES( out[2]=in[0]; out[1]=in[1]; out[0]=in[2]; out+=3; in+=3; ) +} +*/ + +template <class T> +static void pack3_888b(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) { + Pt<int32> o32 = (Pt<int32>)out; + while (n>=4) { + o32[0] = (in[0]<<16) | (in[1]<<8) | in[2]; + o32[1] = (in[3]<<16) | (in[4]<<8) | in[5]; + o32[2] = (in[6]<<16) | (in[7]<<8) | in[8]; + o32[3] = (in[9]<<16) | (in[10]<<8) | in[11]; + o32+=4; in+=12; + n-=4; + } + NTIMES( o32[0] = (in[0]<<16) | (in[1]<<8) | in[2]; o32++; in+=3; ) +} + +/* (R,G,B,?) -> B:8,G:8,R:8,0:8 */ +template <class T> +static void pack3_bgrn8888(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) { +/* NTIMES( out[2]=in[0]; out[1]=in[1]; out[0]=in[2]; out+=4; in+=4; ) */ + Pt<int32> i32 = (Pt<int32>)in; + Pt<int32> o32 = (Pt<int32>)out; + while (n>=4) { + o32[0] = ((i32[0]&0xff)<<16) | (i32[0]&0xff00) | ((i32[0]>>16)&0xff); + o32[1] = ((i32[1]&0xff)<<16) | (i32[1]&0xff00) | ((i32[1]>>16)&0xff); + o32[2] = ((i32[2]&0xff)<<16) | (i32[2]&0xff00) | ((i32[2]>>16)&0xff); + o32[3] = ((i32[3]&0xff)<<16) | (i32[3]&0xff00) | ((i32[3]>>16)&0xff); + o32+=4; i32+=4; n-=4; + } + NTIMES( o32[0] = ((i32[0]&0xff)<<16) | (i32[0]&0xff00) | ((i32[0]>>16)&0xff); o32++; i32++; ) +} + +static uint32 bp_masks[][4] = { + {0x0000f800,0x000007e0,0x0000001f,0}, + {0x00ff0000,0x0000ff00,0x000000ff,0}, +}; + +static Packer bp_packers[] = { + {default_pack, default_pack, default_pack}, + {pack2_565, pack2_565, pack2_565}, + {pack3_888, pack3_888, pack3_888}, + {pack3_888b, default_pack, default_pack}, + {pack3_bgrn8888, default_pack, default_pack}, +}; + +static Unpacker bp_unpackers[] = { + {default_unpack, default_unpack, default_unpack}, +}; + +static BitPacking builtin_bitpackers[] = { + BitPacking(2, 2, 3, bp_masks[0], &bp_packers[1], &bp_unpackers[0]), + BitPacking(1, 3, 3, bp_masks[1], &bp_packers[2], &bp_unpackers[0]), + BitPacking(1, 4, 3, bp_masks[1], &bp_packers[3], &bp_unpackers[0]), + BitPacking(1, 4, 4, bp_masks[1], &bp_packers[4], &bp_unpackers[0]), +}; + +/* **************************************************************** */ + +bool BitPacking::eq(BitPacking *o) { + if (!(bytes == o->bytes)) return false; + if (!(size == o->size)) return false; + for (int i=0; i<size; i++) { + if (!(mask[i] == o->mask[i])) return false; + } + if (endian==o->endian) return true; + /* same==little on a little-endian; same==big on a big-endian */ + return (endian ^ o->endian ^ ::is_le()) == 2; +} + +BitPacking::BitPacking(int endian, int bytes, int size, uint32 *mask, +Packer *packer, Unpacker *unpacker) { + this->endian = endian; + this->bytes = bytes; + this->size = size; + for (int i=0; i<size; i++) this->mask[i] = mask[i]; + if (packer) { + this->packer = packer; + this->unpacker = unpacker; + return; + } + int packeri=-1; + this->packer = &bp_packers[0]; + this->unpacker = &bp_unpackers[0]; + + for (int i=0; i<(int)(sizeof(builtin_bitpackers)/sizeof(BitPacking)); i++) { + BitPacking *bp = &builtin_bitpackers[i]; + if (this->eq(bp)) { + this->packer = bp->packer; + this->unpacker = bp->unpacker; + packeri=i; + goto end; + } + } +end:; +/* + ::gfpost("Bitpacking: endian=%d bytes=%d size=%d packeri=%d", + endian, bytes, size, packeri); + ::gfpost(" packer=0x%08x unpacker=0x%08x",this->packer,this->unpacker); + ::gfpost(" mask=[0x%08x,0x%08x,0x%08x,0x%08x]",mask[0],mask[1],mask[2],mask[3]); +*/ +} + +bool BitPacking::is_le() { + return endian==1 || (endian ^ ::is_le())==3; +} + +template <class T> +void BitPacking::pack(int n, Pt<T> in, Pt<uint8> out) { + switch (NumberTypeE_type_of(*in)) { + case uint8_e: packer->as_uint8(this,n,(Pt<uint8>)in,out); break; + case int16_e: packer->as_int16(this,n,(Pt<int16>)in,out); break; + case int32_e: packer->as_int32(this,n,(Pt<int32>)in,out); break; + default: RAISE("argh"); + } +} + +template <class T> +void BitPacking::unpack(int n, Pt<uint8> in, Pt<T> out) { + switch (NumberTypeE_type_of(*out)) { + case uint8_e: unpacker->as_uint8(this,n,in,(Pt<uint8>)out); break; + case int16_e: unpacker->as_int16(this,n,in,(Pt<int16>)out); break; + case int32_e: unpacker->as_int32(this,n,in,(Pt<int32>)out); break; + default: RAISE("argh"); + } +} + +// i'm sorry... see the end of grid.c for an explanation... +//static +void make_hocus_pocus () { +// exit(1); +#define FOO(S) \ + ((BitPacking*)0)->pack(0,Pt<S>(),Pt<uint8>()); \ + ((BitPacking*)0)->unpack(0,Pt<uint8>(),Pt<S>()); +EACH_NUMBER_TYPE(FOO) +#undef FOO +} diff --git a/externals/gridflow/base/flow_objects.c b/externals/gridflow/base/flow_objects.c new file mode 100644 index 00000000..ac3305d6 --- /dev/null +++ b/externals/gridflow/base/flow_objects.c @@ -0,0 +1,1189 @@ +/* + $Id: flow_objects.c,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include <sys/time.h> +#include <stdlib.h> +#include <math.h> +#include "grid.h.fcs" + +// BAD HACK: GCC complains: unimplemented (--debug|--debug-harder mode only) +#ifdef HAVE_DEBUG +#define SCOPY(a,b,n) COPY(a,b,n) +#else +#define SCOPY(a,b,n) SCopy<n>::f(a,b) +#endif + +template <int n> class SCopy { +public: template <class T> static inline void __attribute__((always_inline)) f(Pt<T> a, Pt<T> b) { + *a=*b; SCopy<n-1>::f(a+1,b+1);}}; +template <> class SCopy<0> { +public: template <class T> static inline void __attribute__((always_inline)) f(Pt<T> a, Pt<T> b) {}}; + +/*template <> class SCopy<4> { +public: template <class T> + static inline void __attribute__((always_inline)) f(Pt<T> a, Pt<T> b) { + *a=*b; SCopy<3>::f(a+1,b+1);} + // wouldn't gcc 2.95 complain here? + static inline void __attribute__((always_inline)) f(Pt<uint8> a, Pt<uint8> b) + { *(int32 *)a=*(int32 *)b; } +};*/ + +Numop *op_add, *op_sub, *op_mul, *op_div, *op_mod, *op_shl, *op_and, *op_put; + +static void expect_dim_dim_list (P<Dim> d) { + if (d->n!=1) RAISE("dimension list should be Dim[n], not %s",d->to_s());} +//static void expect_min_one_dim (P<Dim> d) { +// if (d->n<1) RAISE("minimum 1 dimension");} +static void expect_max_one_dim (P<Dim> d) { + if (d->n>1) { RAISE("expecting Dim[] or Dim[n], got %s",d->to_s()); }} +//static void expect_exactly_one_dim (P<Dim> d) { +// if (d->n!=1) { RAISE("expecting Dim[n], got %s",d->to_s()); }} + +//**************************************************************** +\class GridCast < GridObject +struct GridCast : GridObject { + \attr NumberTypeE nt; + \decl void initialize (NumberTypeE nt); + \grin 0 +}; + +GRID_INLET(GridCast,0) { + out = new GridOutlet(this,0,in->dim,nt); +} GRID_FLOW { + out->send(n,data); +} GRID_END + +\def void initialize (NumberTypeE nt) { + rb_call_super(argc,argv); + this->nt = nt; +} + +\classinfo { IEVAL(rself,"install '#cast',1,1"); } +\end class GridCast + +//**************************************************************** +//{ ?,Dim[B] -> Dim[*Cs] } +// out0 nt to be specified explicitly +\class GridImport < GridObject +struct GridImport : GridObject { + \attr NumberTypeE cast; + \attr P<Dim> dim; // size of grids to send + PtrGrid dim_grid; + GridImport() { dim_grid.constrain(expect_dim_dim_list); } + ~GridImport() {} + \decl void initialize(Ruby x, NumberTypeE cast=int32_e); + \decl void _0_cast(NumberTypeE cast); + \decl void _0_reset(); + \decl void _0_symbol(Symbol x); + \decl void _0_list(...); + \decl void _1_per_message(); + \grin 0 + \grin 1 int32 + template <class T> void process (int n, Pt<T> data) { + while (n) { + if (!out || !out->dim) out = new GridOutlet(this,0,dim?dim:in[0]->dim,cast); + int32 n2 = min((int32)n,out->dim->prod()-out->dex); + out->send(n2,data); + n-=n2; data+=n2; + } + } +}; + +GRID_INLET(GridImport,0) {} GRID_FLOW { process(n,data); } GRID_END +GRID_INPUT(GridImport,1,dim_grid) { dim = dim_grid->to_dim(); } GRID_END + +\def void _0_symbol(Symbol x) { + const char *name = rb_sym_name(argv[0]); + int n = strlen(name); + if (!dim) out=new GridOutlet(this,0,new Dim(n)); + process(n,Pt<uint8>((uint8 *)name,n)); +} + +\def void _0_cast(NumberTypeE cast) { this->cast = cast; } + +\def void _0_list(...) { + if (in.size()<1 || !in[0]) _0_grid(0,0); //HACK: enable grid inlet... + in[0]->from_ruby_list(argc,argv,cast); +} + +\def void _1_per_message() { dim=0; dim_grid=0; } + +\def void initialize(Ruby x, NumberTypeE cast) { + rb_call_super(argc,argv); + this->cast = cast; + if (argv[0]!=SYM(per_message)) { + dim_grid=new Grid(argv[0]); + dim = dim_grid->to_dim(); + } +} + +\def void _0_reset() { + STACK_ARRAY(int32,foo,1); *foo=0; + while (out->dim) out->send(1,foo); +} + +\classinfo { IEVAL(rself,"install '#import',2,1"); } +\end class GridImport + +//**************************************************************** +/*{ Dim[*As] -> ? }*/ +/* in0: integer nt */ +\class GridExport < GridObject +struct GridExport : GridObject { + \grin 0 +}; + +template <class T> +static Ruby INTORFLOAT2NUM(T value) {return INT2NUM(value);} +static Ruby INTORFLOAT2NUM(int64 value) {return gf_ll2num(value);} +static Ruby INTORFLOAT2NUM(float32 value) {return rb_float_new(value);} +static Ruby INTORFLOAT2NUM(float64 value) {return rb_float_new(value);} + +GRID_INLET(GridExport,0) { +} GRID_FLOW { + for (int i=0; i<n; i++) { + Ruby a[] = { INT2NUM(0), INTORFLOAT2NUM(data[i]) }; + send_out(COUNT(a),a); + } +} GRID_END +\classinfo { IEVAL(rself,"install '#export',1,1"); } +\end class GridExport + +/* **************************************************************** */ +/*{ Dim[*As] -> ? }*/ +/* in0: integer nt */ +\class GridExportList < GridObject +struct GridExportList : GridObject { + Ruby /*Array*/ list; + int n; + \grin 0 +}; + +GRID_INLET(GridExportList,0) { + int n = in->dim->prod(); + if (n>250000) RAISE("list too big (%d elements)", n); + list = rb_ary_new2(n+2); + this->n = n; + rb_ivar_set(rself,SI(@list),list); // keep + rb_ary_store(list,0,INT2NUM(0)); + rb_ary_store(list,1,bsym._list); +} GRID_FLOW { + for (int i=0; i<n; i++, data++) + rb_ary_store(list,in->dex+i+2,INTORFLOAT2NUM(*data)); +} GRID_FINISH { + send_out(rb_ary_len(list),rb_ary_ptr(list)); + list = 0; + rb_ivar_set(rself,SI(@list),Qnil); // unkeep +} GRID_END + +\classinfo { IEVAL(rself,"install '#export_list',1,1"); } +\end class GridExportList + +/* **************************************************************** */ +// GridStore ("@store") is the class for storing a grid and restituting +// it on demand. The right inlet receives the grid. The left inlet receives +// either a bang (which forwards the whole image) or a grid describing what +// to send. +//{ Dim[*As,B],Dim[*Cs,*Ds] -> Dim[*As,*Ds] } +// in0: integer nt +// in1: whatever nt +// out0: same nt as in1 +\class GridStore < GridObject +struct GridStore : GridObject { + PtrGrid r; // can't be \attr + PtrGrid put_at; // can't be //\attr + \attr Numop *op; + int32 wdex [Dim::MAX_DIMENSIONS]; // temporary buffer, copy of put_at + int32 fromb[Dim::MAX_DIMENSIONS]; + int32 to2 [Dim::MAX_DIMENSIONS]; + int lsd; // lsd = Last Same Dimension (for put_at) + int d; // goes with wdex + \decl void initialize (Grid *r=0); + \decl void _0_bang (); + \decl void _0_op (Numop *op); + \decl void _1_reassign (); + \decl void _1_put_at (Grid *index); + \grin 0 int + \grin 1 + GridStore() { put_at.constrain(expect_max_one_dim); } + template <class T> void compute_indices(Pt<T> v, int nc, int nd); +}; + +// takes the backstore of a grid and puts it back into place. a backstore +// is a grid that is filled while the grid it would replace has not +// finished being used. +static void snap_backstore (PtrGrid &r) { + if (r.next) {r=r.next.p; r.next=0;} +} + +template <class T> void GridStore::compute_indices(Pt<T> v, int nc, int nd) { + for (int i=0; i<nc; i++) { + uint32 wrap = r->dim->v[i]; + bool fast = lowest_bit(wrap)==highest_bit(wrap); // is power of two? + if (i) { + if (fast) op_shl->map(nd,v,(T)highest_bit(wrap)); + else op_mul->map(nd,v,(T)wrap); + } + if (fast) op_and->map(nd,v+nd*i,(T)(wrap-1)); + else op_mod->map(nd,v+nd*i,(T)(wrap)); + if (i) op_add->zip(nd,v,v+nd*i); + } +} + +// !@#$ i should ensure that n is not exceedingly large +// !@#$ worse: the size of the foo buffer may still be too large +GRID_INLET(GridStore,0) { + // snap_backstore must be done before *anything* else + snap_backstore(r); + int na = in->dim->n; + int nb = r->dim->n; + int nc = in->dim->get(na-1); + STACK_ARRAY(int32,v,Dim::MAX_DIMENSIONS); + if (na<1) RAISE("must have at least 1 dimension.",na,1,1+nb); + int lastindexable = r->dim->prod()/r->dim->prod(nc) - 1; + int ngreatest = nt_greatest((T *)0); + if (lastindexable > ngreatest) { + RAISE("lastindexable=%d > ngreatest=%d (ask matju)",lastindexable,ngreatest); + } + if (nc > nb) + RAISE("wrong number of elements in last dimension: " + "got %d, expecting <= %d", nc, nb); + int nd = nb - nc + na - 1; + COPY(v,in->dim->v,na-1); + COPY(v+na-1,r->dim->v+nc,nb-nc); + out=new GridOutlet(this,0,new Dim(nd,v),r->nt); + if (nc>0) in->set_factor(nc); +} GRID_FLOW { + int na = in->dim->n; + int nc = in->dim->get(na-1); + int size = r->dim->prod(nc); + assert((n % nc) == 0); + int nd = n/nc; + STACK_ARRAY(T,w,n); + Pt<T> v=w; + if (sizeof(T)==1 && nc==1 && r->dim->v[0]<=256) { + // bug? shouldn't modulo be done here? + v=data; + } else { + COPY(v,data,n); + for (int k=0,i=0; i<nc; i++) for (int j=0; j<n; j+=nc) v[k++] = data[i+j]; + compute_indices(v,nc,nd); + } +#define FOO(type) { \ + Pt<type> p = (Pt<type>)*r; \ + if (size<=16) { \ + Pt<type> foo = ARRAY_NEW(type,nd*size); \ + int i=0; \ + switch (size) { \ + case 1: for (; i<nd&-4; i+=4, foo+=4) { \ + foo[0] = p[v[i+0]]; \ + foo[1] = p[v[i+1]]; \ + foo[2] = p[v[i+2]]; \ + foo[3] = p[v[i+3]]; \ + } break; \ + case 2: for (; i<nd; i++, foo+=2) SCOPY(foo,p+2*v[i],2); break; \ + case 3: for (; i<nd; i++, foo+=3) SCOPY(foo,p+3*v[i],3); break; \ + case 4: for (; i<nd; i++, foo+=4) SCOPY(foo,p+4*v[i],4); break; \ + default:; }; \ + for (; i<nd; i++, foo+=size) COPY(foo,p+size*v[i],size); \ + out->give(size*nd,foo-size*nd); \ + } else { \ + for (int i=0; i<nd; i++) out->send(size,p+size*v[i]); \ + } \ +} + TYPESWITCH(r->nt,FOO,) +#undef FOO +} GRID_FINISH { + if (in->dim->prod()==0) { + int n = in->dim->prod(0,-2); + int size = r->dim->prod(); +#define FOO(T) while (n--) out->send(size,(Pt<T>)*r); + TYPESWITCH(r->nt,FOO,) +#undef FOO + } +} GRID_END + +GRID_INLET(GridStore,1) { + NumberTypeE nt = NumberTypeE_type_of(*data); + if (!put_at) { // reassign + if (in[0].dim) + r.next = new Grid(in->dim,nt); + else + r = new Grid(in->dim,nt); + return; + } + // put_at ( ... ) + //!@#$ should check types. if (r->nt!=in->nt) RAISE("shoo"); + int nn=r->dim->n, na=put_at->dim->v[0], nb=in->dim->n; + STACK_ARRAY(int32,sizeb,nn); + for (int i=0; i<nn; i++) { fromb[i]=0; sizeb[i]=1; } + COPY(Pt<int32>(wdex,nn) ,(Pt<int32>)*put_at ,put_at->dim->prod()); + COPY(Pt<int32>(fromb,nn)+nn-na,(Pt<int32>)*put_at ,na); + COPY(Pt<int32>(sizeb,nn)+nn-nb,(Pt<int32>)in->dim->v,nb); + for (int i=0; i<nn; i++) to2[i] = fromb[i]+sizeb[i]; + d=0; + // find out when we can skip computing indices + //!@#$ should actually also stop before blowing up packet size + lsd=nn; + while (lsd>=nn-in->dim->n) { + lsd--; + int cs = in->dim->prod(lsd-nn+in->dim->n); + if (cs>GridOutlet::MAX_PACKET_SIZE || fromb[lsd]!=0 || sizeb[lsd]!=r->dim->v[lsd]) break; + } + lsd++; + int cs = in->dim->prod(lsd-nn+in->dim->n); + in->set_factor(cs); +} GRID_FLOW { + if (!put_at) { // reassign + COPY(((Pt<T>)*(r.next ? r.next.p : &*r.p))+in->dex, data, n); + return; + } + // put_at ( ... ) + int nn=r->dim->n; + int cs = in->factor(); // chunksize + STACK_ARRAY(int32,v,lsd); + Pt<int32> x = Pt<int32>(wdex,nn); + while (n) { + // here d is the dim# to reset; d=n for none + for(;d<lsd;d++) x[d]=fromb[d]; + COPY(v,x,lsd); + compute_indices(v,lsd,1); + op->zip(cs,(Pt<T>)*r+v[0]*cs,data); + data+=cs; + n-=cs; + // find next set of indices; here d is the dim# to increment + for(;;) { + d--; + if (d<0) goto end; + x[d]++; + if (x[d]<to2[d]) break; + } + end:; // why here ??? or why at all? + d++; + } + //end:; // why not here ??? +} GRID_END +\def void _0_op(Numop *op) { this->op=op; } +\def void _0_bang () { rb_funcall(rself,SI(_0_list),3,INT2NUM(0),SYM(#),INT2NUM(0)); } +\def void _1_reassign () { put_at=0; } +\def void _1_put_at (Grid *index) { put_at=index; } +\def void initialize (Grid *r) { + rb_call_super(argc,argv); + this->r = r?r:new Grid(new Dim(),int32_e,true); + op = op_put; +} +\classinfo { IEVAL(rself,"install '#store',2,1"); } +\end class GridStore + +//**************************************************************** +//{ Dim[*As]<T> -> Dim[*As]<T> } or +//{ Dim[*As]<T>,Dim[*Bs]<T> -> Dim[*As]<T> } +\class GridOp < GridObject +struct GridOp : GridObject { + \attr Numop *op; + PtrGrid r; + \decl void initialize(Numop *op, Grid *r=0); + \grin 0 + \grin 1 + \decl void _0_op(Numop *op); +}; + +GRID_INLET(GridOp,0) { + snap_backstore(r); + SAME_TYPE(in,r); + out=new GridOutlet(this,0,in->dim,in->nt); + in->set_mode(6); +} GRID_FLOW { + Pt<T> rdata = (Pt<T>)*r; + int loop = r->dim->prod(); + if (sizeof(T)==8) { + fprintf(stderr,"1: data=%p rdata=%p\n",data.p,rdata.p); + WATCH(n,data); + } + if (loop>1) { + if (in->dex+n <= loop) { + op->zip(n,data,rdata+in->dex); + } else { + // !@#$ should prebuild and reuse this array when "loop" is small + STACK_ARRAY(T,data2,n); + int ii = mod(in->dex,loop); + int m = min(loop-ii,n); + COPY(data2,rdata+ii,m); + int nn = m+((n-m)/loop)*loop; + for (int i=m; i<nn; i+=loop) COPY(data2+i,rdata,loop); + if (n>nn) COPY(data2+nn,rdata,n-nn); + if (sizeof(T)==8) { + fprintf(stderr,"2: data=%p data2=%p\n",data.p,data2.p); + WATCH(n,data); WATCH(n,data2); + } + op->zip(n,data,data2); + if (sizeof(T)==8) {WATCH(n,data); WATCH(n,data2);} + } + } else { + op->map(n,data,*rdata); + } + out->give(n,data); +} GRID_END + +GRID_INPUT2(GridOp,1,r) {} GRID_END +\def void _0_op(Numop *op) { this->op=op; } + +\def void initialize(Numop *op, Grid *r=0) { + rb_call_super(argc,argv); + this->op=op; + this->r = r?r:new Grid(new Dim(),int32_e,true); +} + +\classinfo { IEVAL(rself,"install '#',2,1"); } +\end class GridOp + +//**************************************************************** +\class GridFold < GridObject +struct GridFold : GridObject { + \attr Numop *op; + \attr PtrGrid seed; + \decl void initialize (Numop *op); + \decl void _0_op (Numop *op); + \decl void _0_seed (Grid *seed); + \grin 0 +}; + +GRID_INLET(GridFold,0) { + //{ Dim[*As,B,*Cs]<T>,Dim[*Cs]<T> -> Dim[*As,*Cs]<T> } + if (seed) SAME_TYPE(in,seed); + int an = in->dim->n; + int bn = seed?seed->dim->n:0; + if (an<=bn) RAISE("minimum 1 more dimension than the seed (%d vs %d)",an,bn); + STACK_ARRAY(int32,v,an-1); + int yi = an-bn-1; + COPY(v,in->dim->v,yi); + COPY(v+yi,in->dim->v+an-bn,bn); + if (seed) SAME_DIM(an-(yi+1),in->dim,(yi+1),seed->dim,0); + out=new GridOutlet(this,0,new Dim(an-1,v),in->nt); + int k = seed ? seed->dim->prod() : 1; + in->set_factor(in->dim->get(yi)*k); +} GRID_FLOW { + int an = in->dim->n; + int bn = seed?seed->dim->n:0; + int yn = in->dim->v[an-bn-1]; + int zn = in->dim->prod(an-bn); + STACK_ARRAY(T,buf,n/yn); + int nn=n; + int yzn=yn*zn; + for (int i=0; n; i+=zn, data+=yzn, n-=yzn) { + if (seed) COPY(buf+i,((Pt<T>)*seed),zn); + else CLEAR(buf+i,zn); + op->fold(zn,yn,buf+i,data); + } + out->send(nn/yn,buf); +} GRID_END + +\def void _0_op (Numop *op ) { this->op =op; } +\def void _0_seed (Grid *seed) { this->seed=seed; } +\def void initialize (Numop *op) { rb_call_super(argc,argv); this->op=op; } +\classinfo { IEVAL(rself,"install '#fold',1,1"); } +\end class GridFold + +\class GridScan < GridObject +struct GridScan : GridObject { + \attr Numop *op; + \attr PtrGrid seed; + \decl void initialize (Numop *op); + \decl void _0_op (Numop *op); + \decl void _0_seed (Grid *seed); + \grin 0 +}; + +GRID_INLET(GridScan,0) { + //{ Dim[*As,B,*Cs]<T>,Dim[*Cs]<T> -> Dim[*As,B,*Cs]<T> } + if (seed) SAME_TYPE(in,seed); + int an = in->dim->n; + int bn = seed?seed->dim->n:0; + if (an<=bn) RAISE("minimum 1 more dimension than the right hand"); + if (seed) SAME_DIM(bn,in->dim,an-bn,seed->dim,0); + out=new GridOutlet(this,0,in->dim,in->nt); + in->set_factor(in->dim->prod(an-bn-1)); +} GRID_FLOW { + int an = in->dim->n; + int bn = seed?seed->dim->n:0; + int yn = in->dim->v[an-bn-1]; + int zn = in->dim->prod(an-bn); + int factor = in->factor(); + STACK_ARRAY(T,buf,n); + COPY(buf,data,n); + if (seed) { + for (int i=0; i<n; i+=factor) op->scan(zn,yn,(Pt<T>)*seed,buf+i); + } else { + STACK_ARRAY(T,seed,zn); + CLEAR(seed,zn); + for (int i=0; i<n; i+=factor) op->scan(zn,yn,seed,buf+i); + } + out->send(n,buf); +} GRID_END + +\def void _0_op (Numop *op ) { this->op =op; } +\def void _0_seed (Grid *seed) { this->seed=seed; } +\def void initialize (Numop *op) { rb_call_super(argc,argv); this->op = op; } +\classinfo { IEVAL(rself,"install '#scan',1,1"); } +\end class GridScan + +//**************************************************************** +//{ Dim[*As,C]<T>,Dim[C,*Bs]<T> -> Dim[*As,*Bs]<T> } +\class GridInner < GridObject +struct GridInner : GridObject { + \attr Numop *op_para; + \attr Numop *op_fold; + \attr PtrGrid seed; + PtrGrid r; + PtrGrid r2; + GridInner() {} + \decl void initialize (Grid *r=0); + \decl void _0_op (Numop *op); + \decl void _0_fold (Numop *op); + \decl void _0_seed (Grid *seed); + \grin 0 + \grin 1 +}; + +template <class T> void inner_child_a (Pt<T> buf, Pt<T> data, int rrows, int rcols, int chunk) { + Pt<T> bt = buf, dt = data; + for (int j=0; j<chunk; j++, bt+=rcols, dt+=rrows) op_put->map(rcols,bt,*dt); +} +template <class T, int rcols> void inner_child_b (Pt<T> buf, Pt<T> data, int rrows, int chunk) { + Pt<T> bt = buf, dt = data; + for (int j=0; j<chunk; j++, bt+=rcols, dt+=rrows) { + for (int k=0; k<rcols; k++) bt[k] = *dt; + } +} +GRID_INLET(GridInner,0) { + SAME_TYPE(in,r); + SAME_TYPE(in,seed); + P<Dim> a = in->dim; + P<Dim> b = r->dim; + if (a->n<1) RAISE("a: minimum 1 dimension"); + if (b->n<1) RAISE("b: minimum 1 dimension"); + if (seed->dim->n != 0) RAISE("seed must be a scalar"); + int a_last = a->get(a->n-1); + int n = a->n+b->n-2; + SAME_DIM(1,a,a->n-1,b,0); + STACK_ARRAY(int32,v,n); + COPY(v,a->v,a->n-1); + COPY(v+a->n-1,b->v+1,b->n-1); + out=new GridOutlet(this,0,new Dim(n,v),in->nt); + in->set_factor(a_last); + + int rrows = in->factor(); + int rsize = r->dim->prod(); + int rcols = rsize/rrows; + Pt<T> rdata = (Pt<T>)*r; + int chunk = GridOutlet::MAX_PACKET_SIZE/rsize; + r2=new Grid(new Dim(chunk*rsize),r->nt); + Pt<T> buf3 = (Pt<T>)*r2; + for (int i=0; i<rrows; i++) + for (int j=0; j<chunk; j++) + COPY(buf3+(j+i*chunk)*rcols,rdata+i*rcols,rcols); +} GRID_FLOW { + int rrows = in->factor(); + int rsize = r->dim->prod(); + int rcols = rsize/rrows; + int chunk = GridOutlet::MAX_PACKET_SIZE/rsize; + STACK_ARRAY(T,buf ,chunk*rcols); + STACK_ARRAY(T,buf2,chunk*rcols); + int off = chunk; + while (n) { + if (chunk*rrows>n) chunk=n/rrows; + op_put->map(chunk*rcols,buf2,*(T *)*seed); + for (int i=0; i<rrows; i++) { + switch (rcols) { + case 1: inner_child_b<T,1>(buf,data+i,rrows,chunk); break; + case 2: inner_child_b<T,2>(buf,data+i,rrows,chunk); break; + case 3: inner_child_b<T,3>(buf,data+i,rrows,chunk); break; + case 4: inner_child_b<T,4>(buf,data+i,rrows,chunk); break; + default: inner_child_a(buf,data+i,rrows,rcols,chunk); + } + op_para->zip(chunk*rcols,buf,(Pt<T>)*r2+i*off*rcols); + op_fold->zip(chunk*rcols,buf2,buf); + } + out->send(chunk*rcols,buf2); + n-=chunk*rrows; + data+=chunk*rrows; + } +} GRID_FINISH { + r2=0; +} GRID_END + +GRID_INPUT(GridInner,1,r) {} GRID_END + +\def void initialize (Grid *r) { + rb_call_super(argc,argv); + this->op_para = op_mul; + this->op_fold = op_add; + this->seed = new Grid(new Dim(),int32_e,true); + this->r = r ? r : new Grid(new Dim(),int32_e,true); +} + +\def void _0_op (Numop *op ) { this->op_para=op; } +\def void _0_fold (Numop *op ) { this->op_fold=op; } +\def void _0_seed (Grid *seed) { this->seed=seed; } +\classinfo { IEVAL(rself,"install '#inner',2,1"); } +\end class GridInner + +/* **************************************************************** */ +/*{ Dim[*As]<T>,Dim[*Bs]<T> -> Dim[*As,*Bs]<T> }*/ +\class GridOuter < GridObject +struct GridOuter : GridObject { + \attr Numop *op; + PtrGrid r; + \decl void initialize (Numop *op, Grid *r=0); + \grin 0 + \grin 1 +}; + +GRID_INLET(GridOuter,0) { + SAME_TYPE(in,r); + P<Dim> a = in->dim; + P<Dim> b = r->dim; + int n = a->n+b->n; + STACK_ARRAY(int32,v,n); + COPY(v,a->v,a->n); + COPY(v+a->n,b->v,b->n); + out=new GridOutlet(this,0,new Dim(n,v),in->nt); +} GRID_FLOW { + int b_prod = r->dim->prod(); + if (b_prod > 4) { + STACK_ARRAY(T,buf,b_prod); + while (n) { + for (int j=0; j<b_prod; j++) buf[j] = *data; + op->zip(b_prod,buf,(Pt<T>)*r); + out->send(b_prod,buf); + data++; n--; + } + return; + } + n*=b_prod; + Pt<T> buf = ARRAY_NEW(T,n); + STACK_ARRAY(T,buf2,b_prod*64); + for (int i=0; i<64; i++) COPY(buf2+i*b_prod,(Pt<T>)*r,b_prod); + switch (b_prod) { + #define Z buf[k++]=data[i] + case 1: for (int i=0,k=0; k<n; i++) {Z;} break; + case 2: for (int i=0,k=0; k<n; i++) {Z;Z;} break; + case 3: for (int i=0,k=0; k<n; i++) {Z;Z;Z;} break; + case 4: for (int i=0,k=0; k<n; i++) {Z;Z;Z;Z;} break; + default:for (int i=0,k=0; k<n; i++) for (int j=0; j<b_prod; j++, k++) Z; + } + #undef Z + int ch=64*b_prod; + int nn=(n/ch)*ch; + for (int j=0; j<nn; j+=ch) op->zip(ch,buf+j,buf2); + op->zip(n-nn,buf+nn,buf2); + out->give(n,buf); +} GRID_END + +GRID_INPUT(GridOuter,1,r) {} GRID_END + +\def void initialize (Numop *op, Grid *r) { + rb_call_super(argc,argv); + this->op = op; + this->r = r ? r : new Grid(new Dim(),int32_e,true); +} + +\classinfo { IEVAL(rself,"install '#outer',2,1"); } +\end class GridOuter + +//**************************************************************** +//{ Dim[]<T>,Dim[]<T>,Dim[]<T> -> Dim[A]<T> } or +//{ Dim[B]<T>,Dim[B]<T>,Dim[B]<T> -> Dim[*As,B]<T> } +\class GridFor < GridObject +struct GridFor : GridObject { + \attr PtrGrid from; + \attr PtrGrid to; + \attr PtrGrid step; + GridFor () { + from.constrain(expect_max_one_dim); + to .constrain(expect_max_one_dim); + step.constrain(expect_max_one_dim); + } + \decl void initialize (Grid *from, Grid *to, Grid *step); + \decl void _0_set (Grid *r=0); + \decl void _0_bang (); + \grin 0 int + \grin 1 int + \grin 2 int + template <class T> void trigger (T bogus); +}; + +\def void initialize (Grid *from, Grid *to, Grid *step) { + rb_call_super(argc,argv); + this->from=from; + this->to =to; + this->step=step; +} + +template <class T> +void GridFor::trigger (T bogus) { + int n = from->dim->prod(); + int32 nn[n+1]; + STACK_ARRAY(T,x,64*n); + Pt<T> fromb = (Pt<T>)*from; + Pt<T> tob = (Pt<T>)*to ; + Pt<T> stepb = (Pt<T>)*step; + STACK_ARRAY(T,to2,n); + + for (int i=step->dim->prod()-1; i>=0; i--) + if (!stepb[i]) RAISE("step must not contain zeroes"); + for (int i=0; i<n; i++) { + nn[i] = (tob[i] - fromb[i] + stepb[i] - cmp(stepb[i],(T)0)) / stepb[i]; + if (nn[i]<0) nn[i]=0; + to2[i] = fromb[i]+stepb[i]*nn[i]; + } + P<Dim> d; + if (from->dim->n==0) { d = new Dim(*nn); } + else { nn[n]=n; d = new Dim(n+1,nn); } + int total = d->prod(); + out=new GridOutlet(this,0,d,from->nt); + if (total==0) return; + int k=0; + for(int d=0;;d++) { + // here d is the dim# to reset; d=n for none + for(;d<n;d++) x[k+d]=fromb[d]; + k+=n; + if (k==64*n) {out->send(k,x); k=0; COPY(x,x+63*n,n);} + else { COPY(x+k,x+k-n,n);} + d--; + // here d is the dim# to increment + for(;;d--) { + if (d<0) goto end; + x[k+d]+=stepb[d]; + if (x[k+d]!=to2[d]) break; + } + } + end: if (k) out->send(k,x); +} + +\def void _0_bang () { + SAME_TYPE(from,to); + SAME_TYPE(from,step); + if (!from->dim->equal(to->dim) || !to->dim->equal(step->dim)) + RAISE("dimension mismatch"); +#define FOO(T) trigger((T)0); + TYPESWITCH_NOFLOAT(from->nt,FOO,); +#undef FOO +} + +\def void _0_set (Grid *r) { from=new Grid(argv[0]); } +GRID_INPUT(GridFor,2,step) {} GRID_END +GRID_INPUT(GridFor,1,to) {} GRID_END +GRID_INPUT(GridFor,0,from) {_0_bang(0,0);} GRID_END +\classinfo { IEVAL(rself,"install '#for',3,1"); } +\end class GridFor + +//**************************************************************** +\class GridFinished < GridObject +struct GridFinished : GridObject { + \grin 0 +}; +GRID_INLET(GridFinished,0) { + in->set_mode(0); +} GRID_FINISH { + Ruby a[] = { INT2NUM(0), bsym._bang }; + send_out(COUNT(a),a); +} GRID_END +\classinfo { IEVAL(rself,"install '#finished',1,1"); } +\end class GridFinished + +\class GridDim < GridObject +struct GridDim : GridObject { + \grin 0 +}; +GRID_INLET(GridDim,0) { + GridOutlet out(this,0,new Dim(in->dim->n)); + out.send(in->dim->n,Pt<int32>(in->dim->v,in->dim->n)); + in->set_mode(0); +} GRID_END +\classinfo { IEVAL(rself,"install '#dim',1,1"); } +\end class GridDim + +\class GridType < GridObject +struct GridType : GridObject { + \grin 0 +}; +GRID_INLET(GridType,0) { + Ruby a[] = { INT2NUM(0), SYM(symbol), number_type_table[in->nt].sym }; + send_out(COUNT(a),a); + in->set_mode(0); +} GRID_END +\classinfo { IEVAL(rself,"install '#type',1,1"); } +\end class GridType + +//**************************************************************** +//{ Dim[*As]<T>,Dim[B] -> Dim[*Cs]<T> } +\class GridRedim < GridObject +struct GridRedim : GridObject { + \attr P<Dim> dim; + PtrGrid dim_grid; + PtrGrid temp; // temp->dim is not of the same shape as dim + GridRedim() { dim_grid.constrain(expect_dim_dim_list); } + ~GridRedim() {} + \decl void initialize (Grid *d); + \grin 0 + \grin 1 int32 +}; + +GRID_INLET(GridRedim,0) { + int a = in->dim->prod(), b = dim->prod(); + if (a<b) temp=new Grid(new Dim(a),in->nt); + out=new GridOutlet(this,0,dim,in->nt); +} GRID_FLOW { + int i = in->dex; + if (!temp) { + int b = dim->prod(); + int n2 = min(n,b-i); + if (n2>0) out->send(n2,data); + // discard other values if any + } else { + int a = in->dim->prod(); + int n2 = min(n,a-i); + COPY((Pt<T>)*temp+i,data,n2); + if (n2>0) out->send(n2,data); + } +} GRID_FINISH { + if (!!temp) { + int a = in->dim->prod(), b = dim->prod(); + if (a) { + for (int i=a; i<b; i+=a) out->send(min(a,b-i),(Pt<T>)*temp); + } else { + STACK_ARRAY(T,foo,1); + foo[0]=0; + for (int i=0; i<b; i++) out->send(1,foo); + } + } + temp=0; +} GRID_END + +GRID_INPUT(GridRedim,1,dim_grid) { dim = dim_grid->to_dim(); } GRID_END + +\def void initialize (Grid *d) { + rb_call_super(argc,argv); + dim_grid=d; + dim = dim_grid->to_dim(); +} + +\classinfo { IEVAL(rself,"install '#redim',2,1"); } +\end class GridRedim + +//**************************************************************** +\class GridJoin < GridObject +struct GridJoin : GridObject { + \attr int which_dim; + PtrGrid r; + \grin 0 + \grin 1 + \decl void initialize (int which_dim=-1, Grid *r=0); +}; + +GRID_INLET(GridJoin,0) { + NOTEMPTY(r); + SAME_TYPE(in,r); + P<Dim> d = in->dim; + if (d->n != r->dim->n) RAISE("wrong number of dimensions"); + int w = which_dim; + if (w<0) w+=d->n; + if (w<0 || w>=d->n) + RAISE("can't join on dim number %d on %d-dimensional grids", + which_dim,d->n); + STACK_ARRAY(int32,v,d->n); + for (int i=0; i<d->n; i++) { + v[i] = d->get(i); + if (i==w) { + v[i]+=r->dim->v[i]; + } else { + if (v[i]!=r->dim->v[i]) RAISE("dimensions mismatch: dim #%i, left is %d, right is %d",i,v[i],r->dim->v[i]); + } + } + out=new GridOutlet(this,0,new Dim(d->n,v),in->nt); + if (d->prod(w)) in->set_factor(d->prod(w)); +} GRID_FLOW { + int w = which_dim; + if (w<0) w+=in->dim->n; + int a = in->factor(); + int b = r->dim->prod(w); + Pt<T> data2 = (Pt<T>)*r + in->dex*b/a; + if (a==3 && b==1) { + int m = n+n*b/a; + STACK_ARRAY(T,data3,m); + Pt<T> data4 = data3; + while (n) { + SCOPY(data4,data,3); SCOPY(data4+3,data2,1); + n-=3; data+=3; data2+=1; data4+=4; + } + out->send(m,data3); + } else if (a+b<=16) { + int m = n+n*b/a; + STACK_ARRAY(T,data3,m); + int i=0; + while (n) { + COPY(data3+i,data,a); data+=a; i+=a; n-=a; + COPY(data3+i,data2,b); data2+=b; i+=b; + } + out->send(m,data3); + } else { + while (n) { + out->send(a,data); + out->send(b,data2); + data+=a; data2+=b; n-=a; + } + } +} GRID_FINISH { + if (in->dim->prod()==0) out->send(r->dim->prod(),(Pt<T>)*r); +} GRID_END + +GRID_INPUT(GridJoin,1,r) {} GRID_END + +\def void initialize (int which_dim, Grid *r) { + rb_call_super(argc,argv); + this->which_dim = which_dim; + if (r) this->r=r; +} + +\classinfo { IEVAL(rself,"install '@join',2,1"); } +\end class GridJoin + +//**************************************************************** +\class GridGrade < GridObject +struct GridGrade : GridObject { + \grin 0 +}; + +template <class T> struct GradeFunction { + static int comparator (const void *a, const void *b) { + return **(T**)a - **(T**)b;}}; +#define FOO(S) \ +template <> struct GradeFunction<S> { \ + static int comparator (const void *a, const void *b) { \ + S x = **(S**)a - **(S**)b; \ + return x<0 ? -1 : x>0;}}; +FOO(int64) +FOO(float32) +FOO(float64) +#undef FOO + +GRID_INLET(GridGrade,0) { + out=new GridOutlet(this,0,in->dim,in->nt); + in->set_factor(in->dim->get(in->dim->n-1)); +} GRID_FLOW { + int m = in->factor(); + STACK_ARRAY(T*,foo,m); + STACK_ARRAY(T,bar,m); + for (; n; n-=m,data+=m) { + for (int i=0; i<m; i++) foo[i] = &data[i]; + qsort(foo,m,sizeof(T),GradeFunction<T>::comparator); + for (int i=0; i<m; i++) bar[i] = foo[i]-(T *)data; + out->send(m,bar); + } +} GRID_END + +\classinfo { IEVAL(rself,"install '#grade',1,1"); } +\end class GridGrade + +//**************************************************************** +//\class GridMedian < GridObject +//**************************************************************** + +\class GridTranspose < GridObject +struct GridTranspose : GridObject { + \attr int dim1; + \attr int dim2; + int d1,d2,na,nb,nc,nd; // temporaries + \decl void initialize (int dim1=0, int dim2=1); + \decl void _1_float (int dim1); + \decl void _2_float (int dim2); + \grin 0 +}; + +\def void _1_float (int dim1) { this->dim1=dim1; } +\def void _2_float (int dim2) { this->dim2=dim2; } + +GRID_INLET(GridTranspose,0) { + STACK_ARRAY(int32,v,in->dim->n); + COPY(v,in->dim->v,in->dim->n); + d1=dim1; d2=dim2; + if (d1<0) d1+=in->dim->n; + if (d2<0) d2+=in->dim->n; + if (d1>=in->dim->n || d2>=in->dim->n || d1<0 || d2<0) + RAISE("would swap dimensions %d and %d but this grid has only %d dimensions", + dim1,dim2,in->dim->n); + memswap(v+d1,v+d2,1); + if (d1==d2) { + out=new GridOutlet(this,0,new Dim(in->dim->n,v), in->nt); + } else { + nd = in->dim->prod(1+max(d1,d2)); + nc = in->dim->v[max(d1,d2)]; + nb = in->dim->prod(1+min(d1,d2))/nc/nd; + na = in->dim->v[min(d1,d2)]; + out=new GridOutlet(this,0,new Dim(in->dim->n,v), in->nt); + in->set_factor(na*nb*nc*nd); + } + // Turns a Grid[*,na,*nb,nc,*nd] into a Grid[*,nc,*nb,na,*nd]. +} GRID_FLOW { + STACK_ARRAY(T,res,na*nb*nc*nd); + if (dim1==dim2) { out->send(n,data); return; } + for (; n; n-=na*nb*nc*nd, data+=na*nb*nc*nd) { + for (int a=0; a<na; a++) + for (int b=0; b<nb; b++) + for (int c=0; c<nc; c++) + COPY(res +((c*nb+b)*na+a)*nd, + data+((a*nb+b)*nc+c)*nd,nd); + out->send(na*nb*nc*nd,res); + } +} GRID_END + +\def void initialize (int dim1=0, int dim2=1) { + rb_call_super(argc,argv); + this->dim1 = dim1; + this->dim2 = dim2; +} + +\classinfo { IEVAL(rself,"install '#transpose',3,1"); } +\end class GridTranspose + +//**************************************************************** +\class GridReverse < GridObject +struct GridReverse : GridObject { + \attr int dim1; // dimension to act upon + int d; // temporaries + \decl void initialize (int dim1=0); + \decl void _1_float (int dim1); + \grin 0 +}; + +\def void _1_float (int dim1) { this->dim1=dim1; } + +GRID_INLET(GridReverse,0) { + d=dim1; + if (d<0) d+=in->dim->n; + if (d>=in->dim->n || d<0) + RAISE("would reverse dimension %d but this grid has only %d dimensions", + dim1,in->dim->n); + out=new GridOutlet(this,0,new Dim(in->dim->n,in->dim->v), in->nt); + in->set_factor(in->dim->prod(d)); +} GRID_FLOW { + int f1=in->factor(), f2=in->dim->prod(d+1); + while (n) { + int hf1=f1/2; + Pt<T> data2 = data+f1-f2; + for (int i=0; i<hf1; i+=f2) memswap(data+i,data2-i,f2); + out->send(f1,data); + data+=f1; n-=f1; + } +} GRID_END + +\def void initialize (int dim1=0) { + rb_call_super(argc,argv); + this->dim1 = dim1; +} + +\classinfo { IEVAL(rself,"install '#reverse',2,1"); } +\end class GridReverse + +//**************************************************************** +\class GridCentroid2 < GridObject +struct GridCentroid2 : GridObject { + \decl void initialize (); + \grin 0 int + int sumx,sumy,sum,y; // temporaries +}; + +GRID_INLET(GridCentroid2,0) { + if (in->dim->n != 3) RAISE("expecting 3 dims"); + if (in->dim->v[2] != 1) RAISE("expecting 1 channel"); + in->set_factor(in->dim->prod(1)); + out=new GridOutlet(this,0,new Dim(2), in->nt); + sumx=0; sumy=0; sum=0; y=0; +} GRID_FLOW { + int sx = in->dim->v[1]; + while (n) { + for (int x=0; x<sx; x++) { + sumx+=x*data[x]; + sumy+=y*data[x]; + sum += data[x]; + } + n-=sx; + data+=sx; + y++; + } +} GRID_FINISH { + STACK_ARRAY(int32,blah,2); + blah[0] = sum ? sumy/sum : 0; + blah[1] = sum ? sumx/sum : 0; + out->send(2,blah); +} GRID_END + +\def void initialize () { + rb_call_super(argc,argv); +} + +\classinfo { IEVAL(rself,"install '#centroid2',1,1"); } +\end class GridCentroid2 + +//**************************************************************** +\class GridPerspective < GridObject +struct GridPerspective : GridObject { + \attr int32 z; + \grin 0 + \decl void initialize (int32 z=256); +}; + +GRID_INLET(GridPerspective,0) { + int n = in->dim->n; + STACK_ARRAY(int32,v,n); + COPY(v,in->dim->v,n); + v[n-1]--; + in->set_factor(in->dim->get(in->dim->n-1)); + out=new GridOutlet(this,0,new Dim(n,v),in->nt); +} GRID_FLOW { + int m = in->factor(); + for (; n; n-=m,data+=m) { + op_mul->map(m-1,data,(T)z); + op_div->map(m-1,data,data[m-1]); + out->send(m-1,data); + } +} GRID_END + +\def void initialize (int32 z) {rb_call_super(argc,argv); this->z=z; } +\classinfo { IEVAL(rself,"install '#perspective',1,1"); } +\end class GridPerspective + +static Numop *OP(Ruby x) { return FIX2PTR(Numop,rb_hash_aref(op_dict,x)); } + +void startup_flow_objects () { + op_add = OP(SYM(+)); + op_sub = OP(SYM(-)); + op_mul = OP(SYM(*)); + op_shl = OP(SYM(<<)); + op_mod = OP(SYM(%)); + op_and = OP(SYM(&)); + op_div = OP(SYM(/)); + op_put = OP(SYM(put)); + \startall +} diff --git a/externals/gridflow/base/flow_objects.rb b/externals/gridflow/base/flow_objects.rb new file mode 100644 index 00000000..6ea06c56 --- /dev/null +++ b/externals/gridflow/base/flow_objects.rb @@ -0,0 +1,1476 @@ +=begin + $Id: flow_objects.rb,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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 /#in:/ =~ 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=nil,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 } +GridPack.subclass("@three",3,1) { install_rgrid 0 } +GridPack.subclass("@four", 4,1) { install_rgrid 0 } +GridPack.subclass("@eight",8,1) { install_rgrid 0 } +GridObject.subclass("#unpack",1,0) { + install_rgrid 0, true + def initialize(n) + @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 + send_out 2, t.to_f-t.to_f.floor + 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 +} + +#-------- 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 * + 0","@ >> 8"] + @wires = [-1,0,0,0, 0,0,1,0, 1,0,-1,0] + def update_rotator + rotator = (0...@axis[2]).map {|i| + (0...@axis[2]).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| + rotator[@axis[i]][@axis[j]] = + (scale*Math.cos(th+(j-i)*Math::PI/2)).to_i + } + } + @fobjects[0].send_in 2, + @axis[2], @axis[2], "#".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,total] + 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 +} + +#-------- 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 + +if const_defined? :USB + +class<<USB + attr_reader :busses +end + +class DelcomUSB < GridFlow::FObject + Vendor,Product=0x0FC5,0x1222 + def self.find + r=[] + USB.busses.each {|dir,bus| + bus.each {|dev| + r<<dev if dev.idVendor==Vendor and dev.idProduct==Product + } + } + r + end + def initialize #(bus=nil,dev=nil) + r=DelcomUSB.find + raise "no such device" if r.length<1 + raise "#{r.length} such devices (which one???)" if r.length>1 + @usb=USB.new(r[0]) + if_num=nil + r[0].config.each {|config| + config.interface.each {|interface| + if_num = interface.bInterfaceNumber + } + } + # post "Interface # %i\n", if_num + @usb.set_configuration 1 + @usb.claim_interface if_num + @usb.set_altinterface 0 rescue ArgumentError + end + # libdelcom had this: + # uint8 recipient, deviceModel, major, minor, dataL, dataM; + # uint16 length; uint8[8] extension; + def _0_send_command(major,minor,dataL,dataM,extension="\0\0\0\0\0\0\0\0") + raise "closed" if not @usb + raise "extension.length!=8" if extension.length!=8 + @usb.control_msg( + 0x000000c8, 0x00000012, + minor*0x100+major, + dataM*0x100+dataL, + extension, 5000) + end + def delete; @usb.close; end + install "delcomusb", 1, 1 +end + +# Klippeltronics +FObject.subclass("multio",1,1) { + Vendor,Product=0xDEAD,0xBEEF + def self.find + r=[] + USB.busses.each {|dir,bus| + bus.each {|dev| + post "dir=%s, vendor=%x, product=%x", + dir, dev.idVendor, dev.idProduct + r<<dev if dev.idVendor==Vendor and dev.idProduct==Product + } + } + r + end + def initialize + r=self.class.find + raise "no such device" if r.length<1 + raise "#{r.length} such devices (which one???)" if r.length>1 + $iobox=@usb=USB.new(r[0]) + if_num=nil + r[0].config.each {|config| + config.interface.each {|interface| + #post "interface=%s", interface.to_s + if_num = interface.bInterfaceNumber + } + } + # post "Interface # %i\n", if_num + # @usb.set_configuration 0 + @usb.claim_interface if_num + @usb.set_altinterface 0 rescue ArgumentError + end + #@usb.control_msg(0b10100001,0x01,0,0,"",1000) + #@usb.control_msg(0b10100001,0x01,0,1," ",0) + def delete; @usb.close; end +} +end # if const_defined? :USB + +# 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 +} + +(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) { + # 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 +} + +# 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 +} + +end # module GridFlow diff --git a/externals/gridflow/base/flow_objects_for_image.c b/externals/gridflow/base/flow_objects_for_image.c new file mode 100644 index 00000000..2041c3e1 --- /dev/null +++ b/externals/gridflow/base/flow_objects_for_image.c @@ -0,0 +1,618 @@ +/* + $Id: flow_objects_for_image.c,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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. +*/ + +#include <math.h> +#include "grid.h.fcs" + +static void expect_picture (P<Dim> d) { + if (d->n!=3) RAISE("(height,width,chans) dimensions please");} +static void expect_rgb_picture (P<Dim> d) { + expect_picture(d); + if (d->get(2)!=3) RAISE("(red,green,blue) channels please");} +static void expect_rgba_picture (P<Dim> d) { + expect_picture(d); + if (d->get(2)!=4) RAISE("(red,green,blue,alpha) channels please");} +static void expect_max_one_dim (P<Dim> d) { + if (d->n>1) { RAISE("expecting Dim[] or Dim[n], got %s",d->to_s()); }} + +//**************************************************************** +//{ Dim[A,B,*Cs]<T>,Dim[D,E]<T> -> Dim[A,B,*Cs]<T> } + +static void expect_convolution_matrix (P<Dim> d) { + if (d->n != 2) RAISE("only exactly two dimensions allowed for now (got %d)", + d->n); +} + +// entry in a compiled convolution kernel +struct PlanEntry { int y,x; bool neutral; }; + +\class GridConvolve < GridObject +struct GridConvolve : GridObject { + \attr Numop *op_para; + \attr Numop *op_fold; + \attr PtrGrid seed; + \attr PtrGrid b; + PtrGrid a; + int plann; + PlanEntry *plan; //Pt? + int margx,margy; // margins + GridConvolve () : plan(0) { b.constrain(expect_convolution_matrix); plan=0; } + \decl void initialize (Grid *r=0); + \decl void _0_op (Numop *op); + \decl void _0_fold (Numop *op); + \decl void _0_seed (Grid *seed); + \grin 0 + \grin 1 + template <class T> void copy_row (Pt<T> buf, int sx, int y, int x); + template <class T> void make_plan (T bogus); + ~GridConvolve () {if (plan) delete[] plan;} +}; + +template <class T> void GridConvolve::copy_row (Pt<T> buf, int sx, int y, int x) { + int day = a->dim->get(0), dax = a->dim->get(1), dac = a->dim->prod(2); + y=mod(y,day); x=mod(x,dax); + Pt<T> ap = (Pt<T>)*a + y*dax*dac; + while (sx) { + int sx1 = min(sx,dax-x); + COPY(buf,ap+x*dac,sx1*dac); + x=0; + buf += sx1*dac; + sx -= sx1; + } +} + +static Numop *OP(Ruby x) {return FIX2PTR(Numop,rb_hash_aref(op_dict,x));} + +template <class T> void GridConvolve::make_plan (T bogus) { + P<Dim> da = a->dim, db = b->dim; + int dby = db->get(0); + int dbx = db->get(1); + if (plan) delete[] plan; + plan = new PlanEntry[dbx*dby]; + int i=0; + for (int y=0; y<dby; y++) { + for (int x=0; x<dbx; x++) { + T rh = ((Pt<T>)*b)[y*dbx+x]; + bool neutral = op_para->on(rh)->is_neutral(rh,at_right); + bool absorbent = op_para->on(rh)->is_absorbent(rh,at_right); + STACK_ARRAY(T,foo,1); + if (absorbent) { + foo[0] = 0; + op_para->map(1,foo,rh); + absorbent = op_fold->on(rh)->is_neutral(foo[0],at_right); + } + if (absorbent) continue; + plan[i].y = y; + plan[i].x = x; + plan[i].neutral = neutral; + i++; + } + } + plann = i; +} + +GRID_INLET(GridConvolve,0) { + SAME_TYPE(in,b); + SAME_TYPE(in,seed); + P<Dim> da = in->dim, db = b->dim; + if (!db) RAISE("right inlet has no grid"); + if (!seed) RAISE("seed missing"); + if (db->n != 2) RAISE("right grid must have two dimensions"); + if (da->n < 2) RAISE("left grid has less than two dimensions"); + if (seed->dim->n != 0) RAISE("seed must be scalar"); + if (da->get(0) < db->get(0)) RAISE("grid too small (y): %d < %d", da->get(0), db->get(0)); + if (da->get(1) < db->get(1)) RAISE("grid too small (x): %d < %d", da->get(1), db->get(1)); + margy = (db->get(0)-1)/2; + margx = (db->get(1)-1)/2; + a=new Grid(in->dim,in->nt); + out=new GridOutlet(this,0,da,in->nt); +} GRID_FLOW { + COPY((Pt<T>)*a+in->dex, data, n); +} GRID_FINISH { + Numop *op_put = OP(SYM(put)); + make_plan((T)0); + int dbx = b->dim->get(1); + int day = a->dim->get(0); + int n = a->dim->prod(1); + int sx = a->dim->get(1)+dbx-1; + int n2 = sx*a->dim->prod(2); + STACK_ARRAY(T,buf,n); + STACK_ARRAY(T,buf2,n2); + T orh=0; + for (int iy=0; iy<day; iy++) { + op_put->map(n,buf,*(T *)*seed); + for (int i=0; i<plann; i++) { + int jy = plan[i].y; + int jx = plan[i].x; + T rh = ((Pt<T>)*b)[jy*dbx+jx]; + if (i==0 || plan[i].y!=plan[i-1].y || orh!=rh) { + copy_row(buf2,sx,iy+jy-margy,-margx); + if (!plan[i].neutral) op_para->map(n2,buf2,rh); + } + op_fold->zip(n,buf,buf2+jx*a->dim->prod(2)); + orh=rh; + } + out->send(n,buf); + } + a=0; +} GRID_END + +GRID_INPUT(GridConvolve,1,b) {} GRID_END + +\def void _0_op (Numop *op ) { this->op_para=op; } +\def void _0_fold (Numop *op ) { this->op_fold=op; } +\def void _0_seed (Grid *seed) { this->seed=seed; } + +\def void initialize (Grid *r) { + rb_call_super(argc,argv); + this->op_para = op_mul; + this->op_fold = op_add; + this->seed = new Grid(new Dim(),int32_e,true); + this->b= r ? r : new Grid(new Dim(1,1),int32_e,true); +} + +\classinfo { IEVAL(rself,"install '#convolve',2,1"); } +\end class GridConvolve + +/* ---------------------------------------------------------------- */ +/* "#scale_by" does quick scaling of pictures by integer factors */ +/*{ Dim[A,B,3]<T> -> Dim[C,D,3]<T> }*/ +\class GridScaleBy < GridObject +struct GridScaleBy : GridObject { + \attr PtrGrid scale; // integer scale factor + int scaley; + int scalex; + \decl void initialize (Grid *factor=0); + \grin 0 + \grin 1 + void prepare_scale_factor () { + scaley = ((Pt<int32>)*scale)[0]; + scalex = ((Pt<int32>)*scale)[scale->dim->prod()==1 ? 0 : 1]; + if (scaley<1) scaley=2; + if (scalex<1) scalex=2; + } +}; + +GRID_INLET(GridScaleBy,0) { + P<Dim> a = in->dim; + expect_picture(a); + out=new GridOutlet(this,0,new Dim(a->get(0)*scaley,a->get(1)*scalex,a->get(2)),in->nt); + in->set_factor(a->get(1)*a->get(2)); +} GRID_FLOW { + int rowsize = in->dim->prod(1); + STACK_ARRAY(T,buf,rowsize*scalex); + int chans = in->dim->get(2); + #define Z(z) buf[p+z]=data[i+z] + for (; n>0; data+=rowsize, n-=rowsize) { + int p=0; + #define LOOP(z) \ + for (int i=0; i<rowsize; i+=z) \ + for (int k=0; k<scalex; k++, p+=3) + switch (chans) { + case 3: LOOP(3) {Z(0);Z(1);Z(2);} break; + case 4: LOOP(4) {Z(0);Z(1);Z(2);Z(3);} break; + default: LOOP(chans) {for (int c=0; c<chans; c++) Z(c);} + } + #undef LOOP + for (int j=0; j<scaley; j++) out->send(rowsize*scalex,buf); + } + #undef Z +} GRID_END + +static void expect_scale_factor (P<Dim> dim) { + if (dim->prod()!=1 && dim->prod()!=2) + RAISE("expecting only one or two numbers"); +} + +GRID_INPUT(GridScaleBy,1,scale) { prepare_scale_factor(); } GRID_END + +\def void initialize (Grid *factor) { + scale.constrain(expect_scale_factor); + rb_call_super(argc,argv); + scale=new Grid(INT2NUM(2)); + if (factor) scale=factor; + prepare_scale_factor(); +} + +\classinfo { IEVAL(rself,"install '#scale_by',2,1"); } +\end class GridScaleBy + +// ---------------------------------------------------------------- +//{ Dim[A,B,3]<T> -> Dim[C,D,3]<T> } +\class GridDownscaleBy < GridObject +struct GridDownscaleBy : GridObject { + \attr PtrGrid scale; + \attr bool smoothly; + int scaley; + int scalex; + PtrGrid temp; + \decl void initialize (Grid *factor=0, Symbol option=Qnil); + \grin 0 + \grin 1 + void prepare_scale_factor () { + scaley = ((Pt<int32>)*scale)[0]; + scalex = ((Pt<int32>)*scale)[scale->dim->prod()==1 ? 0 : 1]; + if (scaley<1) scaley=2; + if (scalex<1) scalex=2; + } +}; + +GRID_INLET(GridDownscaleBy,0) { + + P<Dim> a = in->dim; + if (a->n!=3) RAISE("(height,width,chans) please"); + out=new GridOutlet(this,0,new Dim(a->get(0)/scaley,a->get(1)/scalex,a->get(2)),in->nt); + in->set_factor(a->get(1)*a->get(2)); + // i don't remember why two rows instead of just one. + temp=new Grid(new Dim(2,in->dim->get(1)/scalex,in->dim->get(2)),in->nt); +} GRID_FLOW { + int rowsize = in->dim->prod(1); + int rowsize2 = temp->dim->prod(1); + Pt<T> buf = (Pt<T>)*temp; //!@#$ maybe should be something else than T ? + int xinc = in->dim->get(2)*scalex; + int y = in->dex / rowsize; + int chans=in->dim->get(2); + #define Z(z) buf[p+z]+=data[i+z] + if (smoothly) { + while (n>0) { + if (y%scaley==0) CLEAR(buf,rowsize2); + #define LOOP(z) \ + for (int i=0,p=0; p<rowsize2; p+=z) \ + for (int j=0; j<scalex; j++,i+=z) + switch (chans) { + case 1: LOOP(1) {Z(0);} break; + case 2: LOOP(2) {Z(0);Z(1);} break; + case 3: LOOP(3) {Z(0);Z(1);Z(2);} break; + case 4: LOOP(4) {Z(0);Z(1);Z(2);Z(3);} break; + default:LOOP(chans) {for (int k=0; k<chans; k++) Z(k);} break; + } + #undef LOOP + y++; + if (y%scaley==0 && out->dim) { + op_div->map(rowsize2,buf,(T)(scalex*scaley)); + out->send(rowsize2,buf); + CLEAR(buf,rowsize2); + } + data+=rowsize; + n-=rowsize; + } + #undef Z + } else { + #define Z(z) buf[p+z]=data[i+z] + for (; n>0 && out->dim; data+=rowsize, n-=rowsize,y++) { + if (y%scaley!=0) continue; + #define LOOP(z) for (int i=0,p=0; p<rowsize2; i+=xinc, p+=z) + switch(in->dim->get(2)) { + case 1: LOOP(1) {Z(0);} break; + case 2: LOOP(2) {Z(0);Z(1);} break; + case 3: LOOP(3) {Z(0);Z(1);Z(2);} break; + case 4: LOOP(4) {Z(0);Z(1);Z(2);Z(3);} break; + default:LOOP(chans) {for (int k=0; k<chans; k++) Z(k);}break; + } + #undef LOOP + out->send(rowsize2,buf); + } + } + #undef Z +} GRID_END + +GRID_INPUT(GridDownscaleBy,1,scale) { prepare_scale_factor(); } GRID_END + +\def void initialize (Grid *factor, Symbol option) { + scale.constrain(expect_scale_factor); + rb_call_super(argc,argv); + scale=new Grid(INT2NUM(2)); + if (factor) scale=factor; + prepare_scale_factor(); + smoothly = option==SYM(smoothly); +} + +\classinfo { IEVAL(rself,"install '#downscale_by',2,1"); } +\end class GridDownscaleBy + +//**************************************************************** +\class GridLayer < GridObject +struct GridLayer : GridObject { + PtrGrid r; + GridLayer() { r.constrain(expect_rgb_picture); } + \grin 0 int + \grin 1 int +}; + +GRID_INLET(GridLayer,0) { + NOTEMPTY(r); + SAME_TYPE(in,r); + P<Dim> a = in->dim; + expect_rgba_picture(a); + if (a->get(1)!=r->dim->get(1)) RAISE("same width please"); + if (a->get(0)!=r->dim->get(0)) RAISE("same height please"); + in->set_factor(a->prod(2)); + out=new GridOutlet(this,0,r->dim); +} GRID_FLOW { + Pt<T> rr = ((Pt<T>)*r) + in->dex*3/4; + STACK_ARRAY(T,foo,n*3/4); +#define COMPUTE_ALPHA(c,a) \ + foo[j+c] = (data[i+c]*data[i+a] + rr[j+c]*(256-data[i+a])) >> 8 + for (int i=0,j=0; i<n; i+=4,j+=3) { + COMPUTE_ALPHA(0,3); + COMPUTE_ALPHA(1,3); + COMPUTE_ALPHA(2,3); + } +#undef COMPUTE_ALPHA + out->send(n*3/4,foo); +} GRID_END + +GRID_INPUT(GridLayer,1,r) {} GRID_END + +\classinfo { IEVAL(rself,"install '#layer',2,1"); } +\end class GridLayer + +// **************************************************************** +// pad1,pad2 only are there for 32-byte alignment +struct Line { int32 y1,x1,y2,x2,x,m,pad1,pad2; }; + +static void expect_polygon (P<Dim> d) { + if (d->n!=2 || d->get(1)!=2) RAISE("expecting Dim[n,2] polygon"); +} + +\class DrawPolygon < GridObject +struct DrawPolygon : GridObject { + \attr Numop *op; + \attr PtrGrid color; + \attr PtrGrid polygon; + PtrGrid color2; + PtrGrid lines; + int lines_start; + int lines_stop; + DrawPolygon() { + color.constrain(expect_max_one_dim); + polygon.constrain(expect_polygon); + } + \decl void initialize (Numop *op, Grid *color=0, Grid *polygon=0); + \grin 0 + \grin 1 + \grin 2 int32 + void init_lines(); + +}; + +void DrawPolygon::init_lines () { + int nl = polygon->dim->get(0); + lines=new Grid(new Dim(nl,8), int32_e); + Pt<Line> ld = Pt<Line>((Line *)(int32 *)*lines,nl); + Pt<int32> pd = *polygon; + for (int i=0,j=0; i<nl; i++) { + ld[i].y1 = pd[j+0]; + ld[i].x1 = pd[j+1]; + j=(j+2)%(2*nl); + ld[i].y2 = pd[j+0]; + ld[i].x2 = pd[j+1]; + if (ld[i].y1>ld[i].y2) memswap(Pt<int32>(ld+i)+0,Pt<int32>(ld+i)+2,2); + } +} + +static int order_by_starting_scanline (const void *a, const void *b) { + return ((Line *)a)->y1 - ((Line *)b)->y1; +} + +static int order_by_column (const void *a, const void *b) { + return ((Line *)a)->x - ((Line *)b)->x; +} + +GRID_INLET(DrawPolygon,0) { + NOTEMPTY(color); + NOTEMPTY(polygon); + NOTEMPTY(lines); + SAME_TYPE(in,color); + if (in->dim->n!=3) RAISE("expecting 3 dimensions"); + if (in->dim->get(2)!=color->dim->get(0)) + RAISE("image does not have same number of channels as stored color"); + out=new GridOutlet(this,0,in->dim,in->nt); + lines_start = lines_stop = 0; + in->set_factor(in->dim->get(1)*in->dim->get(2)); + int nl = polygon->dim->get(0); + qsort((int32 *)*lines,nl,sizeof(Line),order_by_starting_scanline); + int cn = color->dim->prod(); + color2=new Grid(new Dim(cn*16), color->nt); + for (int i=0; i<16; i++) COPY((Pt<T>)*color2+cn*i,(Pt<T>)*color,cn); +} GRID_FLOW { + int nl = polygon->dim->get(0); + Pt<Line> ld = Pt<Line>((Line *)(int32 *)*lines,nl); + int f = in->factor(); + int y = in->dex/f; + int cn = color->dim->prod(); + Pt<T> cd = (Pt<T>)*color2; + + while (n) { + while (lines_stop != nl && ld[lines_stop].y1<=y) lines_stop++; + for (int i=lines_start; i<lines_stop; i++) { + if (ld[i].y2<=y) { + memswap(ld+i,ld+lines_start,1); + lines_start++; + } + } + if (lines_start == lines_stop) { + out->send(f,data); + } else { + int32 xl = in->dim->get(1); + Pt<T> data2 = ARRAY_NEW(T,f); + COPY(data2,data,f); + for (int i=lines_start; i<lines_stop; i++) { + Line &l = ld[i]; + l.x = l.x1 + (y-l.y1)*(l.x2-l.x1+1)/(l.y2-l.y1+1); + } + qsort(ld+lines_start,lines_stop-lines_start, + sizeof(Line),order_by_column); + for (int i=lines_start; i<lines_stop-1; i+=2) { + int xs = max(ld[i].x,(int32)0), xe = min(ld[i+1].x,xl); + if (xs>=xe) continue; /* !@#$ WHAT? */ + while (xe-xs>=16) { op->zip(16*cn,data2+cn*xs,cd); xs+=16; } + op->zip((xe-xs)*cn,data2+cn*xs,cd); + } + out->give(f,data2); + } + n-=f; + data+=f; + y++; + } +} GRID_END + +GRID_INPUT(DrawPolygon,1,color) {} GRID_END +GRID_INPUT(DrawPolygon,2,polygon) {init_lines();} GRID_END + +\def void initialize (Numop *op, Grid *color, Grid *polygon) { + rb_call_super(argc,argv); + this->op = op; + if (color) this->color=color; + if (polygon) { this->polygon=polygon; init_lines(); } +} + +\classinfo { IEVAL(rself,"install '#draw_polygon',3,1"); } +\end class DrawPolygon + +//**************************************************************** +static void expect_position(P<Dim> d) { + if (d->n!=1) RAISE("position should have 1 dimension, not %d", d->n); + if (d->v[0]!=2) RAISE("position dim 0 should have 2 elements, not %d", d->v[0]); +} + +\class DrawImage < GridObject +struct DrawImage : GridObject { + \attr Numop *op; + \attr PtrGrid image; + \attr PtrGrid position; + \attr bool alpha; + \attr bool tile; + + DrawImage() : alpha(false), tile(false) { + position.constrain(expect_position); + image.constrain(expect_picture); + } + + \decl void initialize (Numop *op, Grid *image=0, Grid *position=0); + \decl void _0_alpha (bool v=true); + \decl void _0_tile (bool v=true); + \grin 0 + \grin 1 + \grin 2 int32 + // draw row # ry of right image in row buffer buf, starting at xs + // overflow on both sides has to be handled automatically by this method + template <class T> void draw_segment(Pt<T> obuf, Pt<T> ibuf, int ry, int x0); +}; + +#define COMPUTE_ALPHA(c,a) obuf[j+(c)] = ibuf[j+(c)] + (rbuf[a])*(obuf[j+(c)]-ibuf[j+(c)])/256; +#define COMPUTE_ALPHA4(b) \ + COMPUTE_ALPHA(b+0,b+3); \ + COMPUTE_ALPHA(b+1,b+3); \ + COMPUTE_ALPHA(b+2,b+3); \ + obuf[b+3] = rbuf[b+3] + (255-rbuf[b+3])*(ibuf[j+b+3])/256; + +template <class T> void DrawImage::draw_segment(Pt<T> obuf, Pt<T> ibuf, int ry, int x0) { + if (ry<0 || ry>=image->dim->get(0)) return; // outside of image + int sx = in[0]->dim->get(1), rsx = image->dim->get(1); + int sc = in[0]->dim->get(2), rsc = image->dim->get(2); + Pt<T> rbuf = (Pt<T>)*image + ry*rsx*rsc; + if (x0>sx || x0<=-rsx) return; // outside of buffer + int n=rsx; + if (x0+n>sx) n=sx-x0; + if (x0<0) { rbuf-=rsc*x0; n+=x0; x0=0; } + if (alpha && rsc==4 && sc==3) { // RGB by RGBA //!@#$ optimise + int j=sc*x0; + for (; n; n--, rbuf+=4, j+=3) { + op->zip(sc,obuf+j,rbuf); COMPUTE_ALPHA(0,3); COMPUTE_ALPHA(1,3); COMPUTE_ALPHA(2,3); + } + } else if (alpha && rsc==4 && sc==4) { // RGBA by RGBA + op->zip(n*rsc,obuf+x0*rsc,rbuf); + int j=sc*x0; + for (; n>=4; n-=4, rbuf+=16, j+=16) { + COMPUTE_ALPHA4(0);COMPUTE_ALPHA4(4); + COMPUTE_ALPHA4(8);COMPUTE_ALPHA4(12); + } + for (; n; n--, rbuf+=4, j+=4) { + COMPUTE_ALPHA4(0); + } + } else { // RGB by RGB, etc + op->zip(n*rsc,obuf+sc*x0,rbuf); + } +} + +GRID_INLET(DrawImage,0) { + NOTEMPTY(image); + NOTEMPTY(position); + SAME_TYPE(in,image); + if (in->dim->n!=3) RAISE("expecting 3 dimensions"); + int lchan = in->dim->get(2); + int rchan = image->dim->get(2); + if (alpha && rchan!=4) { + RAISE("alpha mode works only with 4 channels in right_hand"); + } + if (lchan != rchan-(alpha?1:0) && lchan != rchan) { + RAISE("right_hand has %d channels, alpha=%d, left_hand has %d, expecting %d or %d", + rchan, alpha?1:0, lchan, rchan-(alpha?1:0), rchan); + } + out=new GridOutlet(this,0,in->dim,in->nt); + in->set_factor(in->dim->get(1)*in->dim->get(2)); +} GRID_FLOW { + int f = in->factor(); + int y = in->dex/f; + if (position->nt != int32_e) RAISE("position has to be int32"); + int py = ((int32*)*position)[0], rsy = image->dim->v[0], sy=in->dim->get(0); + int px = ((int32*)*position)[1], rsx = image->dim->v[1], sx=in->dim->get(1); + for (; n; y++, n-=f, data+=f) { + int ty = div2(y-py,rsy); + if (tile || ty==0) { + Pt<T> data2 = ARRAY_NEW(T,f); + COPY(data2,data,f); + if (tile) { + for (int x=px-div2(px+rsx-1,rsx)*rsx; x<sx; x+=rsx) { + draw_segment(data2,data,mod(y-py,rsy),x); + } + } else { + draw_segment(data2,data,y-py,px); + } + out->give(f,data2); + } else { + out->send(f,data); + } + } +} GRID_END + +GRID_INPUT(DrawImage,1,image) {} GRID_END +GRID_INPUT(DrawImage,2,position) {} GRID_END +\def void _0_alpha (bool v=true) { alpha = v; gfpost("ALPHA=%d",v); } +\def void _0_tile (bool v=true) { tile = v; } + +\def void initialize (Numop *op, Grid *image, Grid *position) { + rb_call_super(argc,argv); + this->op = op; + if (image) this->image=image; + if (position) this->position=position; + else this->position=new Grid(new Dim(2),int32_e,true); +} + +\classinfo { IEVAL(rself,"install '#draw_image',3,1"); } +\end class DrawImage + +void startup_flow_objects_for_image () { + \startall +} diff --git a/externals/gridflow/base/flow_objects_for_matrix.c b/externals/gridflow/base/flow_objects_for_matrix.c new file mode 100644 index 00000000..d245a10f --- /dev/null +++ b/externals/gridflow/base/flow_objects_for_matrix.c @@ -0,0 +1,77 @@ +/* + $Id: flow_objects_for_matrix.c,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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. +*/ + +#include <math.h> +#include "grid.h.fcs" + +// produce an upper triangular matrix with ones on the diagonal +// will also affect any additional columns using the same row-operations + +void expect_complete_matrix (P<Dim> d) { + if (d->n!=2) RAISE("bletch"); + if (d->get(0)>d->get(1)) RAISE("argh"); +} + +\class GridMatrixSolve < GridObject +struct GridMatrixSolve : GridObject { + Numop *op_sub; + Numop *op_mul; + Numop *op_div; + PtrGrid matrix; + GridMatrixSolve() { + matrix.constrain(expect_complete_matrix); + } + \decl void initialize (); + \grin 0 float +}; + +GRID_INPUT(GridMatrixSolve,0,matrix) { + int n = matrix->dim->get(0); // # rows + int m = matrix->dim->get(1); // # columns + Pt<T> mat = (Pt<T>)*matrix; + for (int j=0; j<n; j++) { + op_div->map(m,mat+j*m,mat[j*m+j]); + for (int i=j+1; i<n; i++) { + STACK_ARRAY(T,row,m); + COPY(row,mat+j,m); + op_mul->map(m,row,mat[i*m+j]); + op_sub->zip(m,mat+i*m,row); + } + } + GridOutlet out(this,0,matrix->dim); + out.send(n*m,mat); +} GRID_END + +\def void initialize () { + rb_call_super(argc,argv); + this->op_sub = op_sub; + this->op_mul = op_mul; + this->op_div = op_div; +} + +\classinfo { IEVAL(rself,"install '#matrix_solve',1,1"); } +\end class + +void startup_flow_objects_for_matrix () { + \startall +} diff --git a/externals/gridflow/base/grid.c b/externals/gridflow/base/grid.c new file mode 100644 index 00000000..877a460f --- /dev/null +++ b/externals/gridflow/base/grid.c @@ -0,0 +1,604 @@ +/* + $Id: grid.c,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include <stdlib.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> +#include "grid.h.fcs" +#include <ctype.h> + +/* copied from bridge/puredata.c (sorry: linkage issue) */ +struct Pointer : CObject { void *p; Pointer(void *_p) : p(_p) {}}; +Ruby Pointer_s_noo (void *ptr) { + return Data_Wrap_Struct(EVAL("GridFlow::Pointer"), 0, 0, new Pointer(ptr));} +static void *Pointer_gut (Ruby rself) {DGS(Pointer); return self->p;} + +//#define TRACE fprintf(stderr,"%s %s [%s:%d]\n",INFO(parent),__PRETTY_FUNCTION__,__FILE__,__LINE__);assert(this); +#define TRACE assert(this); + +#define CHECK_TYPE(d) \ + if (NumberTypeE_type_of(d)!=this->nt) RAISE("%s(%s): " \ + "type mismatch during transmission (got %s expecting %s)", \ + INFO(parent), \ + __PRETTY_FUNCTION__, \ + number_type_table[NumberTypeE_type_of(d)].name, \ + number_type_table[this->nt].name); + +#define CHECK_BUSY(s) \ + if (!dim) RAISE("%s: " #s " not busy",INFO(parent)); + +#define CHECK_ALIGN(d) \ + {int bytes = number_type_table[nt].size/8; \ + int align = ((long)(void*)d)%bytes; \ + if (align) {L;gfpost("%s(%s): Alignment Warning: %p is not %d-aligned: %d", \ + INFO(parent), __PRETTY_FUNCTION__, (void*)d,bytes,align);}} + +#define CHECK_ALIGN2(d,nt) \ + {int bytes = number_type_table[nt].size/8; \ + int align = ((long)(void*)d)%bytes; \ + if (align) {L;gfpost("Alignment Warning: %p is not %d-aligned: %d", \ + (void*)d,bytes,align);}} + +// **************** Grid ****************************************** + +#define FOO(S) static inline void NUM(Ruby x, S &y) {y=convert(x,(int32*)0);} +EACH_INT_TYPE(FOO) +#undef FOO + +#define FOO(S) \ +static inline void NUM(Ruby x, S &y) { \ + if (TYPE(x)==T_FLOAT) y = RFLOAT(x)->value; \ + else if (INTEGER_P(x)) y = convert(x,(S*)0); \ + else RAISE("expected Float (or at least Integer)");} +EACH_FLOAT_TYPE(FOO) +#undef FOO + +void Grid::init_from_ruby_list(int n, Ruby *a, NumberTypeE nt) { + Ruby delim = SYM(#); + for (int i=0; i<n; i++) { + if (a[i] == delim) { + STACK_ARRAY(int32,v,i); + if (i!=0 && TYPE(a[i-1])==T_SYMBOL) nt=NumberTypeE_find(a[--i]); + for (int j=0; j<i; j++) v[j] = convert(a[j],(int32*)0); + init(new Dim(i,v),nt); + CHECK_ALIGN2(this->data,nt); + if (a[i] != delim) i++; + i++; a+=i; n-=i; + goto fill; + } + } + if (n!=0 && TYPE(a[0])==T_SYMBOL) { + nt = NumberTypeE_find(a[0]); + a++, n--; + } + init(new Dim(n),nt); + CHECK_ALIGN2(this->data,nt); + fill: + int nn = dim->prod(); + n = min(n,nn); +#define FOO(type) { \ + Pt<type> p = (Pt<type>)*this; \ + if (n==0) CLEAR(p,nn); \ + else { \ + for (int i=0; i<n; i++) NUM(a[i],p[i]); \ + for (int i=n; i<nn; i+=n) COPY(p+i,p,min(n,nn-i)); }} + TYPESWITCH(nt,FOO,) +#undef FOO +} + +void Grid::init_from_ruby(Ruby x) { + if (TYPE(x)==T_ARRAY) { + init_from_ruby_list(rb_ary_len(x),rb_ary_ptr(x)); + } else if (INTEGER_P(x) || FLOAT_P(x)) { + init(new Dim(),int32_e); + CHECK_ALIGN2(this->data,nt); + ((Pt<int32>)*this)[0] = INT(x); + } else { + rb_funcall( + EVAL("proc{|x| raise \"can't convert to grid: #{x.inspect}\"}"), + SI(call),1,x); + } +} + +// **************** GridInlet ************************************* + +// must be set before the end of GRID_BEGIN phase, and so cannot be changed +// afterwards. This is to allow some optimisations. Anyway there is no good reason +// why this would be changed afterwards. +void GridInlet::set_factor(int factor) { + if(!dim) RAISE("huh?"); + if(factor<=0) RAISE("%s: factor=%d should be >= 1",INFO(parent),factor); + if (dim->prod() && dim->prod() % factor) + RAISE("%s: set_factor: expecting divisor",INFO(parent)); + if (factor > 1) { + buf=new Grid(new Dim(factor), nt); + bufi=0; + } else { + buf=0; + } +} + +static Ruby GridInlet_begin_1(GridInlet *self) { +#define FOO(T) self->gh->flow(self,-1,Pt<T>()); break; + TYPESWITCH(self->nt,FOO,) +#undef FOO + return Qnil; +} + +static Ruby GridInlet_begin_2(GridInlet *self) { + self->dim = 0; // hack + return (Ruby) 0; +} + +bool GridInlet::supports_type(NumberTypeE nt) { +#define FOO(T) return !! gh->flow_##T; + TYPESWITCH(nt,FOO,return false) +#undef FOO +} + +Ruby GridInlet::begin(int argc, Ruby *argv) {TRACE; + if (!argc) return PTR2FIX(this); + GridOutlet *back_out = (GridOutlet *) Pointer_gut(argv[0]); + nt = (NumberTypeE) INT(argv[1]); + argc-=2, argv+=2; + PROF(parent) { + if (dim) { + gfpost("%s: grid inlet conflict; aborting %s in favour of %s", + INFO(parent), INFO(sender), INFO(back_out->parent)); + abort(); + } + sender = back_out->parent; + if ((int)nt<0 || (int)nt>=(int)number_type_table_end) + RAISE("%s: inlet: unknown number type",INFO(parent)); + if (!supports_type(nt)) + RAISE("%s: number type %s not supported here", + INFO(parent), number_type_table[nt].name); + STACK_ARRAY(int32,v,argc); + for (int i=0; i<argc; i++) v[i] = NUM2INT(argv[i]); + P<Dim> dim = this->dim = new Dim(argc,v); + dex=0; + buf=0; + int r = rb_ensure( + (RMethod)GridInlet_begin_1,(Ruby)this, + (RMethod)GridInlet_begin_2,(Ruby)this); + if (!r) {abort(); goto hell;} + this->dim = dim; + back_out->callback(this); + hell:;} // PROF + return Qnil; +} + +template <class T> void GridInlet::flow(int mode, int n, Pt<T> data) {TRACE; + CHECK_BUSY(inlet); + CHECK_TYPE(*data); + CHECK_ALIGN(data); + PROF(parent) { + if (this->mode==0) {dex += n; return;} // ignore data + if (n==0) return; // no data + switch(mode) { + case 4:{ + int d = dex + bufi; + if (d+n > dim->prod()) { + gfpost("grid input overflow: %d of %d from [%s] to [%s]", + d+n, dim->prod(), INFO(sender), 0); + n = dim->prod() - d; + if (n<=0) return; + } + int bufn = factor(); + if (buf && bufi) { + Pt<T> bufd = (Pt<T>)*buf; + int k = min(n,bufn-bufi); + COPY(bufd+bufi,data,k); + bufi+=k; data+=k; n-=k; + if (bufi==bufn) { + int newdex = dex+bufn; + if (this->mode==6) { + Pt<T> data2 = ARRAY_NEW(T,bufn); + COPY(data2,bufd,bufn); + CHECK_ALIGN(data2); + gh->flow(this,bufn,data2); + } else { + CHECK_ALIGN(bufd); + gh->flow(this,bufn,bufd); + } + dex = newdex; + bufi = 0; + } + } + int m = (n/bufn)*bufn; + if (m) { + int newdex = dex + m; + if (this->mode==6) { + Pt<T> data2 = ARRAY_NEW(T,m); + COPY(data2,data,m); + CHECK_ALIGN(data2); + gh->flow(this,m,data2); + } else { + gh->flow(this,m,data); + } + dex = newdex; + } + data += m; + n -= m; + if (buf && n>0) COPY((Pt<T>)*buf+bufi,data,n), bufi+=n; + }break; + case 6:{ + assert(!buf); + int newdex = dex + n; + gh->flow(this,n,data); + if (this->mode==4) delete[] (T *)data; + dex = newdex; + }break; + case 0: break; // ignore data + default: RAISE("%s: unknown inlet mode",INFO(parent)); + }} // PROF +} + +void GridInlet::end() {TRACE; + assert(this); + if (!dim) RAISE("%s: inlet not busy",INFO(parent)); + if (dim->prod() != dex) { + gfpost("incomplete grid: %d of %d from [%s] to [%s]", + dex, dim->prod(), INFO(sender), INFO(parent)); + } + PROF(parent) { +#define FOO(T) gh->flow(this,-2,Pt<T>()); + TYPESWITCH(nt,FOO,) +#undef FOO + } // PROF + dim=0; + buf=0; + dex=0; +} + +template <class T> void GridInlet::from_grid2(Grid *g, T foo) {TRACE; + nt = g->nt; + dim = g->dim; + int n = g->dim->prod(); + gh->flow(this,-1,Pt<T>()); + if (n>0 && this->mode!=0) { + Pt<T> data = (Pt<T>)*g; + CHECK_ALIGN(data); + int size = g->dim->prod(); + if (this->mode==6) { + Pt<T> d = data; + data = ARRAY_NEW(T,size); // problem with int64,float64 here. + COPY(data,d,size); + CHECK_ALIGN(data); + gh->flow(this,n,data); + } else { + int ntsz = number_type_table[nt].size; + int m = GridOutlet::MAX_PACKET_SIZE/*/ntsz*//factor(); + if (!m) m++; + m *= factor(); + while (n) { + if (m>n) m=n; + CHECK_ALIGN(data); + gh->flow(this,m,data); + data+=m; n-=m; dex+=m; + } + } + } + gh->flow(this,-2,Pt<T>()); + //!@#$ add error handling. + // rescue; abort(); end ??? + dim = 0; + dex = 0; +} + +void GridInlet::from_grid(Grid *g) {TRACE; + if (!supports_type(g->nt)) + RAISE("%s: number type %s not supported here", + INFO(parent), number_type_table[g->nt].name); +#define FOO(T) from_grid2(g,(T)0); + TYPESWITCH(g->nt,FOO,) +#undef FOO +} + +/* **************** GridOutlet ************************************ */ + +void GridOutlet::begin(int woutlet, P<Dim> dim, NumberTypeE nt) {TRACE; + int n = dim->count(); + this->nt = nt; + this->dim = dim; + Ruby a[n+4]; + a[0] = INT2NUM(woutlet); + a[1] = bsym._grid; + a[2] = Pointer_s_noo(this); + a[3] = INT2NUM(nt); + for(int i=0; i<n; i++) a[4+i] = INT2NUM(dim->get(i)); + parent->send_out(COUNT(a),a); + frozen=true; + if (!dim->prod()) {end(); return;} + int32 lcm_factor = 1; + for (uint32 i=0; i<inlets.size(); i++) lcm_factor = lcm(lcm_factor,inlets[i]->factor()); + if (nt != buf->nt) { + // biggest packet size divisible by lcm_factor + int32 v = (MAX_PACKET_SIZE/lcm_factor)*lcm_factor; + if (v==0) v=MAX_PACKET_SIZE; // factor too big. don't have a choice. + buf=new Grid(new Dim(v),nt); + } +} + +// send modifies dex; send_direct doesn't +template <class T> +void GridOutlet::send_direct(int n, Pt<T> data) {TRACE; + assert(data); assert(frozen); + CHECK_BUSY(outlet); CHECK_TYPE(*data); CHECK_ALIGN(data); + for (; n>0; ) { + int pn = min(n,MAX_PACKET_SIZE); + for (uint32 i=0; i<inlets.size(); i++) inlets[i]->flow(4,pn,data); + data+=pn, n-=pn; + } +} + +void GridOutlet::flush() {TRACE; + if (!bufi) return; +#define FOO(T) send_direct(bufi,(Pt<T>)*buf); + TYPESWITCH(buf->nt,FOO,) +#undef FOO + bufi = 0; +} + +template <class T, class S> +static void convert_number_type(int n, Pt<T> out, Pt<S> in) { + for (int i=0; i<n; i++) out[i]=(T)in[i]; +} + +//!@#$ buffering in outlet still is 8x faster...? +//!@#$ should use BitPacking for conversion...? +// send modifies dex; send_direct doesn't +template <class T> +void GridOutlet::send(int n, Pt<T> data) {TRACE; + assert(data); assert(frozen); + if (!n) return; + CHECK_BUSY(outlet); CHECK_ALIGN(data); + if (NumberTypeE_type_of(*data)!=nt) { + int bs = MAX_PACKET_SIZE; +#define FOO(T) { \ + STACK_ARRAY(T,data2,bs); \ + for (;n>=bs;n-=bs,data+=bs) { \ + convert_number_type(bs,data2,data); send(bs,data2);} \ + convert_number_type(n,data2,data); \ + send(n,data2); } + TYPESWITCH(nt,FOO,) +#undef FOO + } else { + dex += n; + assert(dex <= dim->prod()); + if (n > MIN_PACKET_SIZE || bufi + n > MAX_PACKET_SIZE) flush(); + if (n > MIN_PACKET_SIZE) { + send_direct(n,data); + } else { + COPY((Pt<T>)*buf+bufi,data,n); + bufi += n; + } + if (dex==dim->prod()) end(); + } +} + +template <class T> +void GridOutlet::give(int n, Pt<T> data) {TRACE; + assert(data); CHECK_BUSY(outlet); assert(frozen); + assert(dex+n <= dim->prod()); CHECK_ALIGN(data); + if (NumberTypeE_type_of(*data)!=nt) { + send(n,data); + delete[] (T *)data; + return; + } + if (inlets.size()==1 && inlets[0]->mode == 6) { + // this is the copyless buffer passing + flush(); + inlets[0]->flow(6,n,data); + dex += n; + } else { + flush(); + send_direct(n,data); + dex += n; + delete[] (T *)data; + } + if (dex==dim->prod()) end(); +} + +void GridOutlet::callback(GridInlet *in) {TRACE; + CHECK_BUSY(outlet); assert(!frozen); + int mode = in->mode; + assert(mode==6 || mode==4 || mode==0); + inlets.push_back(in); +} + +\class GridObject < FObject + +//!@#$ does not handle types properly +//!@#$ most possibly a big hack +template <class T> +void GridObject_r_flow(GridInlet *in, int n, Pt<T> data) { + GridObject *self = in->parent; + uint32 i; + for (i=0; i<self->in.size(); i++) if (in==self->in[i].p) break; + if (i==self->in.size()) RAISE("inlet not found?"); + if (n==-1) { + rb_funcall(self->rself,SI(send_in),2,INT2NUM(i),SYM(rgrid_begin)); + } else if (n>=0) { + Ruby buf = rb_str_new((char *)((uint8 *)data),n*sizeof(T)); + rb_funcall(self->rself,SI(send_in),3,INT2NUM(i),SYM(rgrid_flow),buf); + } else { + rb_funcall(self->rself,SI(send_in),2,INT2NUM(i),SYM(rgrid_end)); + } +} + +\def Symbol inlet_nt (int inln) { + if (inln<0 || inln>=(int)in.size()) RAISE("bad inlet number"); + P<GridInlet> inl = in[inln]; + if (!inl) RAISE("no such inlet #%d",inln); + if (!inl->dim) return Qnil; + return number_type_table[inl->nt].sym; +} + +\def Array inlet_dim (int inln) { + if (inln<0 || inln>=(int)in.size()) RAISE("bad inlet number"); + P<GridInlet> inl = in[inln]; + if (!inl) RAISE("no such inlet #%d",inln); + if (!inl->dim) return Qnil; + int n=inl->dim->count(); + Ruby a = rb_ary_new2(n); + for(int i=0; i<n; i++) rb_ary_push(a,INT2NUM(inl->dim->v[i])); + return a; +} + +\def void inlet_set_factor (int inln, int factor) { + if (inln<0 || inln>=(int)in.size()) RAISE("bad inlet number"); + P<GridInlet> inl = in[inln]; + if (!inl) RAISE("no such inlet #%d",inln); + if (!inl->dim) RAISE("inlet #%d not active",inln); + inl->set_factor(factor); +} + +\def void send_out_grid_begin (int outlet, Array buf, NumberTypeE nt=int32_e) { + if (outlet<0) RAISE("bad outlet number"); + int n = rb_ary_len(buf); + Ruby *p = rb_ary_ptr(buf); + STACK_ARRAY(int32,v,n); + for (int i=0; i<n; i++) v[i] = convert(p[i],(int32*)0); + out = new GridOutlet(this,outlet,new Dim(n,v),nt); // valgrind says leak? +} + +template <class T> +void send_out_grid_flow_2(P<GridOutlet> go, Ruby s, T bogus) { + int n = rb_str_len(s) / sizeof(T); + Pt<T> p = rb_str_pt(s,T); + go->send(n,p); +} + +\def void send_out_grid_flow (int outlet, String buf, NumberTypeE nt=int32_e) { + if (outlet<0) RAISE("bad outlet number"); +#define FOO(T) send_out_grid_flow_2(out,argv[1],(T)0); + TYPESWITCH(nt,FOO,) +#undef FOO +} + +// install_rgrid(Integer inlet, Boolean multi_type? = true) +static Ruby GridObject_s_install_rgrid(int argc, Ruby *argv, Ruby rself) { + if (argc<1 || argc>2) RAISE("er..."); + IEVAL(rself,"@handlers||=[]"); + Ruby handlers = rb_ivar_get(rself,SI(@handlers)); + GridHandler *gh = new GridHandler; + bool mt = argc>1 ? argv[1]==Qtrue : 0; /* multi_type? */ + if (mt) { +#define FOO(S) gh->flow_##S = GridObject_r_flow; +EACH_NUMBER_TYPE(FOO) +#undef FOO + } else { +#define FOO(S) gh->flow_##S = 0; +EACH_NUMBER_TYPE(FOO) +#undef FOO + } + gh->flow_int32 = GridObject_r_flow; + //IEVAL(rself,"self.class_eval { def _0_grid(*a) ___grid(0,*a) end }"); + rb_funcall(handlers,SI([]=),2,INT2NUM(INT(argv[0])),PTR2FIX(gh)); + return Qnil; +} + +static Ruby GridObject_s_instance_methods(int argc, Ruby *argv, Ruby rself) { + static const char *names[] = {"grid","list","float"}; + Ruby list = rb_class_instance_methods(argc,argv,rself); + Ruby handlers = rb_ivar_get(rself,SI(@handlers)); + if (handlers==Qnil) return list; + for (int i=0; i<rb_ary_len(handlers); i++) { + Ruby ghp = rb_ary_ptr(handlers)[i]; + if (ghp==Qnil) continue; + GridHandler *gh = FIX2PTR(GridHandler,ghp); + char buf[256]; + for (int j=0; j<COUNT(names); j++) { + sprintf(buf,"_%d_%s",i,names[j]); + rb_ary_push(list,rb_str_new2(buf)); + } + } + return list; +} + +// this does auto-conversion of list/float to grid +// this also (will) do grid inputs for ruby stuff. +\def Ruby method_missing (...) { + { + if (argc<1) RAISE("not enough arguments"); + if (!SYMBOL_P(argv[0])) RAISE("expected symbol"); + const char *name = rb_sym_name(argv[0]); + char *endp; + if (*name++!='_') goto hell; + int i = strtol(name,&endp,10); + if (name==endp) goto hell; + if (*endp++!='_') goto hell; + if (strcmp(endp,"grid")==0) { + Ruby handlers = rb_ivar_get(rb_obj_class(rself),SI(@handlers)); + if (TYPE(handlers)!=T_ARRAY) { + rb_p(handlers); + RAISE("gridhandler-list missing (maybe forgot install_rgrid ?)" + " while trying to receive on inlet %d",i); + } + if (i>=rb_ary_len(handlers)) RAISE("BORK"); + GridHandler *gh = FIX2PTR(GridHandler, rb_ary_ptr(handlers)[i]); + if (in.size()<=(uint32)i) in.resize(i+1); + if (!in[i]) in[i]=new GridInlet((GridObject *)this,gh); + return in[i]->begin(argc-1,argv+1); + } + // we call the grid method recursively to ask it its GridInlet* + // don't do this before checking the missing method is exactly that =) + char foo[42]; + sprintf(foo,"_%d_grid",i); + P<GridInlet> inl = FIX2PTR(GridInlet,rb_funcall(rself,rb_intern(foo),0)); + if (strcmp(endp,"list" )==0) return inl->from_ruby_list(argc-1,argv+1), Qnil; + if (strcmp(endp,"float")==0) return inl->from_ruby (argc-1,argv+1), Qnil; + } + hell: return rb_call_super(argc,argv); +} + +\classinfo { + IEVAL(rself,"install 'GridObject',0,0"); + // define in Ruby-metaclass + rb_define_singleton_method(rself,"instance_methods",(RMethod)GridObject_s_instance_methods,-1); + rb_define_singleton_method(rself,"install_rgrid",(RMethod)GridObject_s_install_rgrid,-1); + rb_enable_super(rb_singleton_class(rself),"instance_methods"); +} +\end class GridObject + +Ruby cGridObject; + +void startup_grid () { + \startall + cGridObject = rb_const_get(mGridFlow,SI(GridObject)); +} + +// never call this. this is a hack to make some things work. +// i'm trying to circumvent either a bug in the compiler or i don't have a clue. :-( +void make_gimmick () { + GridOutlet foo(0,0,0); +#define FOO(S) foo.give(0,Pt<S>()); +EACH_NUMBER_TYPE(FOO) +#undef FOO +} + diff --git a/externals/gridflow/base/grid.h b/externals/gridflow/base/grid.h new file mode 100644 index 00000000..4aaf2aa3 --- /dev/null +++ b/externals/gridflow/base/grid.h @@ -0,0 +1,1109 @@ +/* + $Id: grid.h,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#ifndef __GF_GRID_H +#define __GF_GRID_H + +// current version number as string literal +#define GF_VERSION "0.8.0" +#define GF_COMPILE_TIME __DATE__ ", " __TIME__ + +#include <new> +#include <vector> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <stdio.h> +#include <math.h> + +extern "C" { +#include <ruby.h> +#include <rubyio.h> +#include <version.h> +}; + +#ifndef RUBY_H +#error "Can't do anything without ruby.h" +#endif + +#include "../config.h" + +#ifdef __WIN32__ +#define INT winINT +#define random rand +#undef send +#undef close +#define sigjmp_buf jmp_buf +#define siglongjmp longjmp +#endif + +#define BUG(s,args...) {fprintf(stderr,s "\nat: %s\n",args,__PRETTY_FUNCTION__); ::raise(11);} + +// !@#$ what am I going to do about this? should this be changed? +// should I wrap all of the Ruby API for C++-style convenience? +typedef VALUE Ruby; +// typedef struct Ruby { VALUE x }; + +#ifdef IS_BRIDGE +#define RAISE(args...) rb_raise(rb_eArgError,args) +#else +#define RAISE(args...) rb_raise0(__FILE__,__LINE__,__PRETTY_FUNCTION__,rb_eArgError,args) +#endif + +// avoid ruby warning +#ifndef rb_enable_super +#define rb_enable_super(a,b) \ + if (RUBY_RELEASE_CODE < 20030716) rb_enable_super(a,b) +#endif + +/* undocumented function from Ruby that is one thing we need to fix a very elusive bug +that manifests itself when embedding ruby inside a plugin of another app. This exists +for all versions of Ruby up to now, and I don't know when it gets fixed. */ +extern "C" void Init_stack(VALUE *addr); + +extern "C" { +void rb_raise0( +const char *file, int line, const char *func, VALUE exc, const char *fmt, ...) +__attribute__ ((noreturn)); +}; +#define L fprintf(stderr,"%s:%d in %s\n",__FILE__,__LINE__,__PRETTY_FUNCTION__); +#define SI(_sym_) (rb_intern(#_sym_)) +#define SYM(_sym_) (ID2SYM(SI(_sym_))) +#define DGS(_class_) \ + _class_ *self; \ + Data_Get_Struct(rself,_class_,self); \ + self->check_magic(); + +// returns the size of a statically defined array +// warning: does not work with STACK_ARRAY() +#define COUNT(_array_) ((int)(sizeof(_array_) / sizeof((_array_)[0]))) + +static inline long rb_str_len(Ruby s) {return RSTRING(s)->len;} +static inline char *rb_str_ptr(Ruby s) {return RSTRING(s)->ptr;} +static inline long rb_ary_len(Ruby s) {return RARRAY(s)->len;} +static inline Ruby *rb_ary_ptr(Ruby s) {return RARRAY(s)->ptr;} +static inline const char *rb_sym_name(Ruby sym) {return rb_id2name(SYM2ID(sym));} +#define rb_str_pt(s,t) Pt<t>((t*)rb_str_ptr(s),rb_str_len(s)) + +// shorthands +#define IEVAL(_self_,s) rb_funcall(_self_,SI(instance_eval),1,rb_str_new2(s)) +#define EVAL(s) rb_eval_string(s) +#define rassert(_p_) if (!(_p_)) RAISE(#_p_); + +// because of older versions of Ruby (1.6.?) +#define rb_obj_class(o) rb_funcall((o),SI(class),0) + +#define WATCH(n,ar) { \ + char foo[16*1024], *p=foo; \ + p += sprintf(p,"%s: ",#ar); \ + for (int q=0; q<n; q++) p += sprintf(p,"%lld ",(long long)ar[q]); \ + gfpost("%s",foo); \ +} + +// we're gonna override assert, so load it first, to avoid conflicts +#include <assert.h> + +#undef assert +#define assert(_expr_) \ + if (!(_expr_)) { \ + fprintf(stderr, "%s:%d: assertion failed: %s is false\n", \ + __FILE__, __LINE__, #_expr_); \ + ::abort(); } + +// disabling assertion checking? +#ifndef HAVE_DEBUG +#undef assert +#define assert(_foo_) +#endif + +#ifdef HAVE_TSC_PROFILING +#define HAVE_PROFILING +#endif + +#ifdef HAVE_PROFILING +#define PROF(_self_) for (GFStackMarker gf_marker(_self_);gf_marker.once();) +#else +#define PROF(_self_) +#endif // HAVE_PROFILING + +static inline Ruby PTR2FIX (const void *ptr) { + long p = (long)ptr; + if ((p&3)!=0) BUG("unaligned pointer: %08x\n",(long)(ptr)); + return LONG2NUM(p>>2); +} +#define FIX2PTR(type,ruby) ((type *)(TO(long,ruby)<<2)) + +//**************************************************************** + +/* int32 was long before, now int, because of amd64 */ +typedef char int8; typedef unsigned char uint8; +typedef short int16; typedef unsigned short uint16; +typedef int int32; typedef unsigned int uint32; +typedef long long int64;typedef unsigned long long uint64; +typedef float float32; +typedef double float64; + +// three-way comparison (T is assumed Comparable) +template <class T> static inline T cmp(T a, T b) { return a<b ? -1 : a>b; } + +// a remainder function such that div2(a,b)*b+mod(a,b) = a and for which +// mod(a,b) is in [0;b) or (b;0]. in contrast to C-language builtin a%b, +// this one has uniform behaviour around zero. +static inline int mod(int a, int b) { +int c=a%b; c+=b&-(c&&(a<0)^(b<0)); return c;} + +// counterpart of mod(a,b), just like a/b and a%b are counterparts +static inline int div2(int a, int b) { +int c=a<0; return (a/b)-(c&&!!(a%b));} + +static inline int32 gf_abs( int32 a) { return a>0?a:-a; } +static inline int64 gf_abs( int64 a) { return a>0?a:-a; } +static inline float32 gf_abs(float32 a) { return fabs(a); } +static inline float64 gf_abs(float64 a) { return fabs(a); } + +// integer powers in log(b) time. T is assumed Integer +template <class T> static inline T ipow(T a, T b) { + for(T r=1;;) {if (b&1) r*=a; b>>=1; if (!b) return r; a*=a;} +} + +// kludge +static inline float32 ipow(float32 a, float32 b) { return pow(a,b); } +static inline float64 ipow(float64 a, float64 b) { return pow(a,b); } + +#undef min +#undef max +// minimum/maximum functions; T is assumed to be Comparable +template <class T> static inline T min(T a, T b) { return a<b?a:b; } +template <class T> static inline T max(T a, T b) { return a>b?a:b; } +//template <class T> inline T min(T a, T b) { T c = (a-b)>>31; return (a&c)|(b&~c); } +//template <class T> inline T max(T a, T b) { T c = (a-b)>>31; return (a&c)|(b&~c); } + +// greatest common divisor, by euclid's algorithm +// this runs in log(a+b) number operations +template <class T> static T gcd (T a, T b) { + while (b) {T c=mod(a,b); a=b; b=c;} + return a; +} + +// greatest common divisor, the binary algorithm. haven't tried yet. +template <class T> static T gcd2 (T a, T b) { + int s=0; + while ((a|b)&1==0) { a>>=1; b>>=1; s++; } + while (a) { + if (a&1==0) a>>=1; + else if (b&1==0) b>>=1; + else {T t=abs(a-b); if (a<b) b=t; else a=t;} + } + return b<<s; +} + +// least common multiple; this runs in log(a+b) like gcd. +template <class T> static inline T lcm (T a, T b) {return a*b/gcd(a,b);} + +// returns the position (0..31) of highest bit set in a word, or 0 if none. +#define FOO(N) if ((x>>N)&(((typeof(x))1<<N)-1)) { x>>=N; i+=N; } +static int highest_bit(uint8 x) {int i=0; FOO(4)FOO(2)FOO(1)return i;} +static int highest_bit(uint16 x) {int i=0; FOO(8)FOO(4)FOO(2)FOO(1)return i;} +static int highest_bit(uint32 x) {int i=0; FOO(16)FOO(8)FOO(4)FOO(2)FOO(1)return i;} +static int highest_bit(uint64 x) {int i=0;FOO(32)FOO(16)FOO(8)FOO(4)FOO(2)FOO(1)return i;} +#undef FOO +// returns the position (0..31) of lowest bit set in a word, or 0 if none. +template <class T> static int lowest_bit(T n) { return highest_bit((~n+1)&n); } + +static double drand() { return 1.0*rand()/(RAND_MAX+1.0); } + +// is little-endian +static inline bool is_le() {int x=1; return ((char *)&x)[0];} + +#if defined(HAVE_PENTIUM) +static inline uint64 rdtsc() { + uint64 x; __asm__ volatile (".byte 0x0f, 0x31":"=A"(x)); return x; +} +#elif defined(HAVE_PPC) +static inline uint64 rdtsc() { +#include <mach/mach.h> +#include <mach/mach_time.h> +/* see AbsoluteToNanoseconds(), Microseconds() +http://www.simdtech.org/apps/group_public/email/altivec/msg01956.html +and mach_absolute_time() and mach_timebase_info(&info), +then ns=(time*info.numer)/info.denom; +and the timebase_info is constant +(double)mach_absolute_time*gTimeBaseToNS*/ +return 0; +} +#else +static inline uint64 rdtsc() {return 0;} +#endif + +#ifdef HAVE_LITE +#define EACH_INT_TYPE(MACRO) MACRO(uint8) MACRO(int16) MACRO(int32) +#define EACH_FLOAT_TYPE(MACRO) +#else +#define EACH_INT_TYPE(MACRO) MACRO(uint8) MACRO(int16) MACRO(int32) MACRO(int64) +#define EACH_FLOAT_TYPE(MACRO) MACRO(float32) MACRO(float64) +#endif +#define EACH_NUMBER_TYPE(MACRO) EACH_INT_TYPE(MACRO) EACH_FLOAT_TYPE(MACRO) + +// note: loop unrolling macros assume N!=0 +// btw this may cause alignment problems when 8 does not divide N +#define UNROLL_8(MACRO,N,PTR,ARGS...) \ + int n__=(-N)&7; PTR-=n__; N+=n__; \ + switch (n__) { start: \ + case 0:MACRO(0); case 1:MACRO(1); case 2:MACRO(2); case 3:MACRO(3); \ + case 4:MACRO(4); case 5:MACRO(5); case 6:MACRO(6); case 7:MACRO(7); \ + PTR+=8; N-=8; ARGS; if (N) goto start; } +#define UNROLL_4(MACRO,N,PTR,ARGS...) \ + int n__=(-N)&3; PTR-=n__; N+=n__; \ + switch (n__) { start: \ + case 0:MACRO(0); case 1:MACRO(1); case 2:MACRO(2); case 3:MACRO(3); \ + PTR+=4; N-=4; ARGS; if (N) goto start; } + +//**************************************************************** +// hook into pointer manipulation. will help find memory corruption bugs. + +template <class T> class Pt { +typedef ptrdiff_t /* and not int nor long */ Z; +public: + T *p; +#ifdef HAVE_DEBUG + T *start; + Z n; + Pt() : p(0), start(0), n(0) {} + Pt(T *q, Z _n) : p(q), start(q), n(_n) {} + Pt(T *q, Z _n, T *_start) : p(q), start(_start), n(_n) {} +#else + Pt() : p(0) {} + Pt(T *q, Z _n, T *_start=0) : p(q) {} +#endif + T &operator *() { return *p; } + Pt operator+=(Z i) { p+=i; return *this; } + Pt operator-=(Z i) { p-=i; return *this; } + Pt operator++( ) { p++; return *this; } + Pt operator--( ) { p--; return *this; } + Pt operator++(int) { Pt f(*this); ++*this; return f; } + Pt operator--(int) { Pt f(*this); --*this; return f; } + T &operator[](Z i) { +#ifdef HAVE_DEBUG_HARDER + if (!(p+i>=start && p+i<start+n)) + BUG("BUFFER OVERFLOW: 0x%08lx[%ld]=0x%08lx is not in 0x%08lx..0x%08lx", + (long)p, (long)i, (long)(p+i),(long)start,(long)(start+n)); +#endif + return p[i]; + } + + void will_use(int k) { +#ifdef HAVE_DEBUG_HARDER + if (k==0) return; + T *q = p+k; + if (!(p>=start && p<start+n && q>=start && q<=start+n)) + BUG("BUFFER OVERFLOW: 0x%08lx...0x%08lx is not all inside 0x%08lx...0x%08lx", + (long)p,(long)q,start,(long)(start+n)); +#endif + } + + Z operator-(Pt x) { return p-x.p; } + operator bool () { return (bool )p; } + operator void *() { return (void *)p; } + operator int8 *() { return (int8 *)p; } + +/* how do i make typecast operators that are not also default conversions??? */ +/* this should be found so i can clean up the following: */ +#define FOO(S) operator S *() { return (S *)p; } +EACH_NUMBER_TYPE(FOO) +#undef FOO +#ifdef HAVE_DEBUG +#define FOO(S) operator Pt<S>() { return Pt<S>((S *)p,n*sizeof(T)/1,(S *)start); } +EACH_NUMBER_TYPE(FOO) +#undef FOO +#else +#define FOO(S) operator Pt<S>() { return Pt<S>((S *)p,0); } +EACH_NUMBER_TYPE(FOO) +#undef FOO +#endif +/* end 0.8 (TESTING) */ + +#ifdef HAVE_DEBUG + template <class U> Pt operator+(U x) { return Pt(p+x,n,start); } + template <class U> Pt operator-(U x) { return Pt(p-x,n,start); } +#else + template <class U> Pt operator+(U x) { return Pt(p+x,0); } + template <class U> Pt operator-(U x) { return Pt(p-x,0); } +#endif +}; + +//template <class T> class P : Pt<T> {}; +//a reference counting pointer class +template <class T> class P { +public: +#define INCR if (p) p->refcount++; +#define DECR if (p) {p->refcount--; if (!p->refcount) delete p;} + T *p; + P() : p(0) {} + P(T *_p) { p = _p; INCR; } + P(const P<T> &_p) { p = _p.p; INCR; } + P<T> &operator =(T * _p) { DECR; p=_p; INCR; return *this; } + P<T> &operator =(P<T> _p) { DECR; p=_p.p; INCR; return *this; } + bool operator ==(P<T> _p) { return p == _p.p; } + bool operator !=(P<T> _p) { return p != _p.p; } + ~P() { DECR; } + bool operator !(){ return !p; } + operator bool() { return !!p; } + T &operator *() { return *p; } + T *operator ->() { return p; } +//#undef INCR +//#undef DECR +}; + +#ifndef IS_BRIDGE +extern "C" void *gfmalloc(size_t n); +extern "C" void gffree(void *p); +inline void *::operator new (size_t n) { return gfmalloc(n); } +inline void *::operator new[] (size_t n) { return gfmalloc(n); } +inline void ::operator delete (void *p) { gffree(p); } +inline void ::operator delete[] (void *p) { gffree(p); } +#endif + +#define STACK_ARRAY(T,V,N) T V##_foo[N]; Pt<T> V(V##_foo,N); +#define ARRAY_NEW(T,N) (Pt<T>((T *)new T[N],N)) + +void gfmemcopy(uint8 *out, const uint8 *in, int n); +template <class T> inline void COPY(Pt<T> dest, Pt<T> src, int n) { + src.will_use(n); dest.will_use(n); + gfmemcopy((uint8*)dest,(const uint8*)src,n*sizeof(T)); +} +template <class T> inline void CLEAR(Pt<T> dest, int n) { + dest.will_use(n); + memset(dest,0,n*sizeof(T)); +} +template <class T> static void memswap (Pt<T> a, Pt<T> b, int n) { + STACK_ARRAY(T,c,n); COPY(c,a,n); COPY(a,b,n); COPY(b,c,n); +} + +//**************************************************************** +// my own little Ruby <-> C++ layer + +struct Arg { Ruby a; }; +struct ArgList { int n; Pt<Arg> v; }; +static inline bool INTEGER_P(Ruby x) {return FIXNUM_P(x)||TYPE(x)==T_BIGNUM;} +static inline bool FLOAT_P(Ruby x) {return TYPE(x)==T_FLOAT;} +#define INT(x) TO(int32,x) +#define TO(t,x) convert(x,(t*)0) + +// not using NUM2INT because Ruby can convert Symbol to int +// (by compatibility with Ruby 1.4) +static inline int32 convert(Ruby x, int32 *foo) { + if (INTEGER_P(x)) return NUM2INT(x); + if (FLOAT_P(x)) return NUM2INT(rb_funcall(x,SI(round),0)); + RAISE("expected Integer or Float (got %s)", + rb_str_ptr(rb_funcall(x,SI(inspect),0))); +} +static int16 convert(Ruby x, int16 *foo) { + int v = INT(x); + if (v<-0x8000 || v>=0x8000) RAISE("value %d is out of range",v); + return v;} +static uint16 convert(Ruby x, uint16 *foo) { + int v = INT(x); + if (v<0 || v>=0x10000) RAISE("value %d is out of range",v); + return v;} +static bool convert(Ruby x, bool *foo) { + if (x==Qtrue) return true; + if (x==Qfalse) return false; + switch (TYPE(x)) { + case T_FIXNUM: case T_BIGNUM: case T_FLOAT: return !!INT(x); + default: RAISE("can't convert to bool"); + } +} + +static uint64 convert(Ruby val, uint64 *foo) { + if (FIXNUM_P(val)) return (uint64)FIX2LONG(val); + if (TYPE(val)!=T_BIGNUM) RAISE("type error"); + uint64 v = (uint64)NUM2UINT(rb_funcall(val,SI(>>),1,INT2FIX(32))) << 32; + return v + NUM2UINT(rb_funcall(val,SI(&),1,UINT2NUM(0xffffffff)));} +static int64 convert(Ruby val, int64 *foo) { + if (FIXNUM_P(val)) return (int64)FIX2LONG(val); + if (TYPE(val)!=T_BIGNUM) RAISE("type error"); + int64 v = (int64)NUM2INT(rb_funcall(val,SI(>>),1,INT2FIX(32))) << 32; + return v + NUM2UINT(rb_funcall(val,SI(&),1,UINT2NUM(0xffffffff)));} + +static Ruby gf_ull2num(uint64 val) { + return rb_funcall( + rb_funcall(UINT2NUM((uint32)(val>>32)),SI(<<),1,INT2FIX(32)), + SI(+),1,UINT2NUM((uint32)val));} +static Ruby gf_ll2num(int64 val) { + return rb_funcall( + rb_funcall(INT2NUM((int32)(val>>32)),SI(<<),1,INT2FIX(32)), + SI(+),1,UINT2NUM((uint32)val));} + +static long convert(Ruby x, long *foo) { + return sizeof(long)==sizeof(int32) ? + convert(x,(int32 *)0) : + convert(x,(int64 *)0); +} +static float64 convert(Ruby x, float64 *foo) { + if (INTEGER_P(x)) return INT(x); + if (TYPE(x)!=T_FLOAT) RAISE("not a Float"); + return ((RFloat*)x)->value;} +static float32 convert(Ruby x, float32 *foo) { + return (float32) convert(x,(float64 *)0);} +typedef Ruby Symbol, Array, String, Integer; +static Ruby convert(Ruby x, Ruby *bogus) { return x; } +typedef Ruby (*RMethod)(...); /* !@#$ fishy */ + +#define BUILTIN_SYMBOLS(MACRO) \ + MACRO(_grid,"grid") MACRO(_bang,"bang") MACRO(_float,"float") \ + MACRO(_list,"list") MACRO(_sharp,"#") \ + MACRO(iv_outlets,"@outlets") \ + MACRO(iv_ninlets,"@ninlets") \ + MACRO(iv_noutlets,"@noutlets") +extern struct BuiltinSymbols { +#define FOO(_sym_,_str_) Ruby _sym_; +BUILTIN_SYMBOLS(FOO) +#undef FOO +} bsym; + +//**************************************************************** +// CObject is the base class for C++ classes that get exported to Ruby. +// BTW: It's quite convenient to have virtual-methods in the base class +// because otherwise the vtable* isn't at the beginning of the object +// and that's pretty confusing to a lot of people, especially when simple +// casting causes a pointer to change its value. +struct CObject { +#define OBJECT_MAGIC 1618033989 + int32 magic; + int32 refcount; + Ruby rself; // point to Ruby peer + CObject() : magic(OBJECT_MAGIC), refcount(0), rself(0) {} + void check_magic () { + if (magic != OBJECT_MAGIC) { + fprintf(stderr,"Object memory corruption! (ask the debugger)\n"); + for (int i=-2; i<=2; i++) + fprintf(stderr,"this[%d]=0x%08x\n",i,((int*)this)[i]); + raise(11); + } + } + virtual ~CObject() { magic = 0xDEADBEEF; } + virtual void mark() {} // not used for now +}; +void CObject_free (void *); + +// you shouldn't use MethodDecl directly (used by source_filter.rb) +struct MethodDecl { const char *selector; RMethod method; }; +void define_many_methods(Ruby rself, int n, MethodDecl *methods); + +extern Ruby mGridFlow, cFObject, cGridObject, cFormat; + +//**************************************************************** +// a Dim is a list of dimensions that describe the shape of a grid +\class Dim < CObject +struct Dim : CObject { + static const int MAX_DIMENSIONS=16; // maximum number of dimensions in a grid + int n; + Pt<int32> v; // safe pointer + int32 v2[MAX_DIMENSIONS]; // real stuff + void check(); // test invariants + Dim(int n, Pt<int32> v) { + this->v = Pt<int32>(v2,MAX_DIMENSIONS); + this->n = n; + COPY(this->v,v,n); check(); + } + Dim(int n, int32* v) { + this->v = Pt<int32>(v2,MAX_DIMENSIONS); + this->n = n; + COPY(this->v,Pt<int32>(v,n),n); check(); + } + Dim() {v=Pt<int32>(v2,MAX_DIMENSIONS); n=0; check();} + Dim(int a) {v=Pt<int32>(v2,MAX_DIMENSIONS); n=1;v[0]=a; check();} + Dim(int a,int b) {v=Pt<int32>(v2,MAX_DIMENSIONS); n=2;v[0]=a;v[1]=b; check();} + Dim(int a,int b,int c){v=Pt<int32>(v2,MAX_DIMENSIONS); n=3;v[0]=a;v[1]=b;v[2]=c;check();} + int count() {return n;} + int get(int i) { return v[i]; } + int32 prod(int start=0, int end=-1) { + if (end<0) end+=n; + int32 tot=1; + for (int i=start; i<=end; i++) tot *= v[i]; + return tot; + } + char *to_s(); + bool equal(P<Dim> o) { + if (n!=o->n) return false; + for (int i=0; i<n; i++) if (v[i]!=o->v[i]) return false; + return true; + } +}; +\end class Dim + +//**************************************************************** +//BitPacking objects encapsulate optimised loops of conversion +struct BitPacking; +// those are the types of the optimised loops of conversion +// inputs are const +struct Packer { +#define FOO(S) void (*as_##S)(BitPacking *self, int n, Pt<S> in, Pt<uint8> out); +EACH_INT_TYPE(FOO) +#undef FOO +}; +struct Unpacker { +#define FOO(S) void (*as_##S)(BitPacking *self, int n, Pt<uint8> in, Pt<S> out); +EACH_INT_TYPE(FOO) +#undef FOO +}; + +\class BitPacking < CObject +struct BitPacking : CObject { + Packer *packer; + Unpacker *unpacker; + unsigned int endian; // 0=big, 1=little, 2=same, 3=different + int bytes; + int size; + uint32 mask[4]; + BitPacking(){::abort();} // don't call, but don't remove. sorry. + BitPacking(int endian, int bytes, int size, uint32 *mask, + Packer *packer=0, Unpacker *unpacker=0); + bool is_le(); + bool eq(BitPacking *o); + \decl void initialize(Ruby foo1, Ruby foo2, Ruby foo3); + \decl String pack2(String ins, String outs=Qnil); + \decl String unpack2(String ins, String outs=Qnil); + \decl String to_s(); +// main entrances to Packers/Unpackers + template <class T> void pack( int n, Pt<T> in, Pt<uint8> out); + template <class T> void unpack(int n, Pt<uint8> in, Pt<T> out); +}; +\end class + +int high_bit(uint32 n); +int low_bit(uint32 n); +void swap32 (int n, Pt<uint32> data); +void swap16 (int n, Pt<uint16> data); + +#define NT_UNSIGNED (1<<0) +#define NT_FLOAT (1<<1) +#define NT_UNIMPL (1<<2) +#define NUMBER_TYPE_LIMITS(T,a,b,c) \ + inline T nt_smallest(T *bogus) {return a;} \ + inline T nt_greatest(T *bogus) {return b;} \ + inline T nt_all_ones(T *bogus) {return c;} + +NUMBER_TYPE_LIMITS( uint8,0,255,255) +NUMBER_TYPE_LIMITS( int16,-0x8000,0x7fff,-1) +NUMBER_TYPE_LIMITS( int32,-0x80000000,0x7fffffff,-1) +NUMBER_TYPE_LIMITS( int64,-0x8000000000000000LL,0x7fffffffffffffffLL,-1) +NUMBER_TYPE_LIMITS(float32,-HUGE_VAL,+HUGE_VAL,(RAISE("all_ones"),0)) +NUMBER_TYPE_LIMITS(float64,-HUGE_VAL,+HUGE_VAL,(RAISE("all_ones"),0)) + +#ifdef HAVE_LITE +#define NT_NOTLITE NT_UNIMPL +#define NONLITE(x...) +#else +#define NT_NOTLITE 0 +#define NONLITE(x...) x +#endif +#define NUMBER_TYPES(MACRO) \ + MACRO( uint8, 8,NT_UNSIGNED, "u8,b") MACRO( int8, 8,NT_UNIMPL, "i8") \ + MACRO( uint16,16,NT_UNSIGNED|NT_UNIMPL, "u16") MACRO(int16,16,0, "i16,s") \ + MACRO( uint32,32,NT_UNSIGNED|NT_UNIMPL, "u32") MACRO(int32,32,0, "i32,i") \ + MACRO( uint64,64,NT_UNSIGNED|NT_UNIMPL, "u64") MACRO(int64,64,NT_NOTLITE, "i64,l") \ + MACRO(float32,32,NT_NOTLITE|NT_FLOAT, "f32,f") \ + MACRO(float64,64,NT_NOTLITE|NT_FLOAT, "f64,d") + +enum NumberTypeE { +#define FOO(_sym_,args...) _sym_##_e, +NUMBER_TYPES(FOO) +#undef FOO + number_type_table_end +}; + +#define FOO(_type_) \ +inline NumberTypeE NumberTypeE_type_of(_type_ &x) { \ + return _type_##_e; } +EACH_NUMBER_TYPE(FOO) +#undef FOO + +\class NumberType < CObject +struct NumberType : CObject { + Ruby /*Symbol*/ sym; + const char *name; + int size; + int flags; + const char *aliases; + NumberTypeE index; + NumberType(const char *name_, int size_, int flags_, const char *aliases_) : + name(name_), size(size_), flags(flags_), aliases(aliases_) {} + \decl Symbol sym_m(); + \decl int size_m(); + \decl int flags_m(); + \decl int index_m(); +}; +\end class + +NumberTypeE NumberTypeE_find (Ruby sym); + +#define TYPESWITCH(T,C,E) switch (T) { \ + case uint8_e: C(uint8) break; case int16_e: C(int16) break; \ + case int32_e: C(int32) break; NONLITE(case int64_e: C(int64) break; \ + case float32_e: C(float32) break; case float64_e: C(float64) break;) \ + default: E; RAISE("type '%s' not available here",number_type_table[T].sym);} +#define TYPESWITCH_NOFLOAT(T,C,E) switch (T) { \ + case uint8_e: C(uint8) break; case int16_e: C(int16) break; \ + case int32_e: C(int32) break; NONLITE(case int64_e: C(int64) break;)\ + default: E; RAISE("type '%s' not available here",number_type_table[T].sym);} + +// Numop objects encapsulate optimised loops of simple operations + +enum LeftRight { at_left, at_right }; + +template <class T> +struct NumopOn : CObject { + // Function Vectorisations + typedef void (*Map )( int n, T *as, T b ); Map op_map; + typedef void (*Zip )( int n, T *as, T *bs); Zip op_zip; + typedef void (*Fold)(int an, int n, T *as, T *bs); Fold op_fold; + typedef void (*Scan)(int an, int n, T *as, T *bs); Scan op_scan; + // Algebraic Properties (those involving simply numeric types) + typedef bool (*AlgebraicCheck)(T x, LeftRight side); + // neutral: right: forall y {f(x,y)=x}; left: forall x {f(x,y)=y}; + // absorbent: right: exists a forall y {f(x,y)=a}; ... + AlgebraicCheck is_neutral, is_absorbent; + NumopOn(Map m, Zip z, Fold f, Scan s, AlgebraicCheck n, AlgebraicCheck a) : + op_map(m), op_zip(z), op_fold(f), op_scan(s), + is_neutral(n), is_absorbent(a) {} + NumopOn() {} + NumopOn(const NumopOn &z) { + op_map = z.op_map; op_zip = z.op_zip; + op_fold = z.op_fold; op_scan = z.op_scan; + is_neutral = z.is_neutral; is_absorbent = z.is_absorbent; } +}; + +// semigroup property: associativity: f(a,f(b,c))=f(f(a,b),c) +#define OP_ASSOC (1<<0) +// abelian property: commutativity: f(a,b)=f(b,a) +#define OP_COMM (1<<1) + +\class Numop < CObject +struct Numop : CObject { + Ruby /*Symbol*/ sym; + const char *name; + int flags; +//private: +#define FOO(T) \ + NumopOn<T> on_##T; \ + NumopOn<T> *on(T &foo) { \ + if (!on_##T.op_map) \ + RAISE("operator %s does not support type "#T,rb_sym_name(sym)); \ + return &on_##T;} +EACH_NUMBER_TYPE(FOO) +#undef FOO +//public: + template <class T> inline void map(int n, Pt<T> as, T b) { + as.will_use(n); + on(*as)->op_map(n,(T *)as,b);} + template <class T> inline void zip(int n, Pt<T> as, Pt<T> bs) { + as.will_use(n); + bs.will_use(n); + on(*as)->op_zip(n,(T *)as,(T *)bs);} + template <class T> inline void fold(int an, int n, Pt<T> as, Pt<T> bs) { + as.will_use(an); + bs.will_use(an*n); + typename NumopOn<T>::Fold f = on(*as)->op_fold; + if (!f) RAISE("operator %s does not support fold",rb_sym_name(sym)); + f(an,n,(T *)as,(T *)bs);} + template <class T> inline void scan(int an, int n, Pt<T> as, Pt<T> bs) { + as.will_use(an); + bs.will_use(an*n); + typename NumopOn<T>::Scan f = on(*as)->op_scan; + if (!f) RAISE("operator %s does not support scan",rb_sym_name(sym)); + f(an,n,(T *)as,(T *)bs);} + + \decl void map_m (NumberTypeE nt, int n, String as, String b); + \decl void zip_m (NumberTypeE nt, int n, String as, String bs); + \decl void fold_m (NumberTypeE nt, int an, int n, String as, String bs); + \decl void scan_m (NumberTypeE nt, int an, int n, String as, String bs); + + Numop(Ruby /*Symbol*/ sym_, const char *name_, +#define FOO(T) NumopOn<T> op_##T, +EACH_NUMBER_TYPE(FOO) +#undef FOO + int flags_) : sym(sym_), name(name_), flags(flags_) { +#define FOO(T) on_##T = op_##T; +EACH_NUMBER_TYPE(FOO) +#undef FOO + } +}; +\end class + +extern NumberType number_type_table[]; +extern Ruby number_type_dict; // GridFlow.@number_type_dict={} +extern Ruby op_dict; // GridFlow.@op_dict={} + +static inline NumberTypeE convert(Ruby x, NumberTypeE *bogus) { + return NumberTypeE_find(x); +} + +#ifndef IS_BRIDGE +static Numop *convert(Ruby x, Numop **bogus) { + Ruby s = rb_hash_aref(rb_ivar_get(mGridFlow,SI(@op_dict)),x); + if (s==Qnil) RAISE("expected two-input-operator"); + return FIX2PTR(Numop,s); +} +#endif + +// **************************************************************** +\class Grid < CObject +struct Grid : CObject { + P<Dim> dim; + NumberTypeE nt; + void *data; + void *rdata; + Grid(P<Dim> dim, NumberTypeE nt, bool clear=false) : dim(0), nt(int32_e), data(0) { + if (!dim) RAISE("hell"); + init(dim,nt); + if (clear) {int size = bytes(); CLEAR(Pt<char>((char *)data,size),size);} + } + Grid(Ruby x) : dim(0), nt(int32_e), data(0) { init_from_ruby(x); } + Grid(int n, Ruby *a, NumberTypeE nt=int32_e) : dim(0), nt(int32_e), data(0) { + init_from_ruby_list(n,a,nt); + } + int32 bytes() { return dim->prod()*number_type_table[nt].size/8; } + P<Dim> to_dim () { return new Dim(dim->prod(),(Pt<int32>)*this); } +#define FOO(type) \ + operator type *() { return (type *)data; } \ + operator Pt<type>() { return Pt<type>((type *)data,dim->prod()); } +EACH_NUMBER_TYPE(FOO) +#undef FOO + Grid *dup () { + Grid *foo=new Grid(dim,nt); + memcpy(foo->data,data,bytes()); + return foo; + } + ~Grid() {if (rdata) delete[] (uint8 *)rdata;} +private: + void init(P<Dim> dim, NumberTypeE nt) { + this->dim = dim; + this->nt = nt; + rdata = dim ? new int64[1+(bytes()+7)/8] : 0; + int align = ((long)rdata) & 7; + data = (char *)rdata + ((8-align)&7); + //fprintf(stderr,"rdata=%p data=%p align=%d\n",rdata,data,align); + } + void init_from_ruby(Ruby x); + void init_from_ruby_list(int n, Ruby *a, NumberTypeE nt=int32_e); +}; +\end class Grid + +static inline Grid *convert (Ruby r, Grid **bogus) { + return r ? new Grid(r) : 0; +} + +// DimConstraint interface: +// return if d is acceptable +// else RAISE with proper descriptive message +typedef void (*DimConstraint)(P<Dim> d); + +struct PtrGrid : public P<Grid> { + DimConstraint dc; + void constrain(DimConstraint dc_) { dc=dc_; } + P<Grid> next; + PtrGrid() : P<Grid>(), dc(0), next(0) {} + PtrGrid(const PtrGrid &_p) : P<Grid>(), dc(0), next(0) {dc=_p.dc; p=_p.p; INCR;} + PtrGrid &operator =( Grid *_p) {if(dc&&p)dc(_p->dim); DECR; p=_p; INCR; return *this;} + PtrGrid &operator =(P<Grid> _p) {if(dc&&p)dc(_p->dim); DECR; p=_p.p; INCR; return *this;} + PtrGrid &operator =(PtrGrid _p) {if(dc&&p)dc(_p->dim); DECR; p=_p.p; INCR; return *this;} +}; + +#ifndef IS_BRIDGE +static inline P<Dim> convert(Ruby x, P<Dim> *foo) { + Grid *d = convert(x,(Grid **)0); + if (!d) RAISE("urgh"); + if (d->dim->n!=1) RAISE("dimension list must have only one dimension itself"); + return new Dim(d->dim->v[0],(int32 *)(d->data)); +} + +static inline PtrGrid convert(Ruby x, PtrGrid *foo) { + PtrGrid pg; + pg = convert(x,(Grid **)0); + return pg; +} +#endif + +//**************************************************************** +// GridInlet represents a grid-aware inlet + +// four-part macro for defining the behaviour of a gridinlet in a class +// C:Class I:Inlet +#define GRID_INLET(C,I) \ + template <class T> void C::grinw_##I (GridInlet *in, int n, Pt<T> data) { \ + ((C*)(in->parent))->grin_##I(in,n,data); } \ + template <class T> void C::grin_##I (GridInlet *in, int n, Pt<T> data) { \ + if (n==-1) +#define GRID_ALLOC else if (n==-3) +#define GRID_FLOW else if (n>=0) +#define GRID_FINISH else if (n==-2) +#define GRID_END } + +/* macro for defining a gridinlet's behaviour as just storage (no backstore) */ +// V is a PtrGrid instance-var +#define GRID_INPUT(C,I,V) \ +GRID_INLET(C,I) { V=new Grid(in->dim,NumberTypeE_type_of(*data)); } \ +GRID_FLOW { COPY((Pt<T>)*(V)+in->dex, data, n); } GRID_FINISH + +// macro for defining a gridinlet's behaviour as just storage (with backstore) +// V is a PtrGrid instance-var +#define GRID_INPUT2(C,I,V) \ + GRID_INLET(C,I) { \ + if (is_busy_except(in)) { \ + V.next = new Grid(in->dim,NumberTypeE_type_of(*data)); \ + } else V= new Grid(in->dim,NumberTypeE_type_of(*data)); \ + } GRID_FLOW { \ + COPY(((Pt<T>)*(V.next?V.next.p:&*V.p))+in->dex, data, n); \ + } GRID_FINISH + +typedef struct GridInlet GridInlet; +typedef struct GridHandler { +#define FOO(T) \ + void (*flow_##T)(GridInlet *in, int n, Pt<T> data); \ + void flow(GridInlet *in, int n, Pt<T> data) const { \ + assert(flow_##T); flow_##T(in,n,data); } +EACH_NUMBER_TYPE(FOO) +#undef FOO +} GridHandler; + +typedef struct GridObject GridObject; +\class GridInlet < CObject +struct GridInlet : CObject { + GridObject *parent; + const GridHandler *gh; + GridObject *sender; + P<Dim> dim; + NumberTypeE nt; + int dex; + PtrGrid buf;// factor-chunk buffer + int bufi; // buffer index: how much of buf is filled + int mode; // 0=ignore; 4=ro; 6=rw + + int allocfactor,allocmin,allocmax,allocn; + Pt<uint8> alloc; + +// methods + GridInlet(GridObject *parent_, const GridHandler *gh_) : + parent(parent_), gh(gh_), sender(0), + dim(0), nt(int32_e), dex(0), bufi(0), mode(4) {} + ~GridInlet() {} + void set_factor(int factor); + void set_mode(int mode_) { mode=mode_; } + int32 factor() {return buf?buf->dim->prod():1;} + Ruby begin(int argc, Ruby *argv); + + // n=-1 is begin, and n=-2 is _finish_. the name "end" is now used + // as an end-marker for inlet definitions... sorry for the confusion + // GF-0.8: n=-3 is alloc. + template <class T> void flow(int mode, int n, Pt<T> data); + void end(); // this one ought to be called finish(). + void from_ruby_list(int argc, Ruby *argv, NumberTypeE nt=int32_e) { + Grid t(argc,argv,nt); from_grid(&t); + } + void from_ruby(int argc, Ruby *argv) { + Grid t(argv[0]); from_grid(&t); + } + void from_grid(Grid *g); + bool supports_type(NumberTypeE nt); +private: + template <class T> void from_grid2(Grid *g, T foo); +}; +\end class GridInlet + +//**************************************************************** +// for use by source_filter.rb ONLY (for \grin and \classinfo) + +// C is for class, I for inlet number +// GRIN1 : int32 only +// GRIN4 : all types +// GRIN2 : integers only; no floats +// GRINF : floats only; no integers +#ifndef HAVE_LITE +#define GRIN(TB,TS,TI,TL,TF,TD) {TB,TS,TI,TL,TF,TD} +#else +#define GRIN(TB,TS,TI,TL,TF,TD) {TB,TS,TI} +#endif // HAVE_LITE + +#define GRIN1(C,I) GRIN(0,0,C::grinw_##I,0,0,0) +#define GRIN4(C,I) GRIN(C::grinw_##I,C::grinw_##I,C::grinw_##I,C::grinw_##I,C::grinw_##I,C::grinw_##I) +#define GRIN2(C,I) GRIN(C::grinw_##I,C::grinw_##I,C::grinw_##I,C::grinw_##I,0,0) +#define GRINF(C,I) GRIN(0,0,0,0,C::grinw_##I,C::grinw_##I) + +struct FClass { // 0.7.8: removed all GridObject-specific stuff. + void *(*allocator)(); // returns a new C++ object + void (*startup)(Ruby rself); // initializer for the Ruby class + const char *name; // C++/Ruby name (not PD name) + int methodsn; MethodDecl *methods; // C++ -> Ruby methods +}; + +//**************************************************************** +// GridOutlet represents a grid-aware outlet +\class GridOutlet < CObject +struct GridOutlet : CObject { +// number of (minimum,maximum) BYTES to send at once +// starting with version 0.8, this is amount of BYTES, not amount of NUMBERS. + static const int MIN_PACKET_SIZE = 1<<8; + static const int MAX_PACKET_SIZE = 1<<12; +// those are set only once + GridObject *parent; // not a P<> because of circular refs + P<Dim> dim; // dimensions of the grid being sent + NumberTypeE nt; + PtrGrid buf; // temporary buffer + bool frozen; // is the "begin" phase finished? + std::vector<GridInlet *> inlets; // which inlets are we connected to +// those are updated during transmission + int dex; // how many numbers were already sent in this connection + int bufi; // number of bytes used in the buffer +// methods + GridOutlet(GridObject *parent_, int woutlet, P<Dim> dim_, NumberTypeE nt_=int32_e) : + parent(parent_), dim(dim_), nt(nt_), frozen(false), dex(0), bufi(0) { + int ntsz = number_type_table[nt].size; + buf=new Grid(new Dim(MAX_PACKET_SIZE/*/ntsz*/), nt); + begin(woutlet,dim,nt); + } + ~GridOutlet() {} + void callback(GridInlet *in); + + // send/send_direct: data belongs to caller, may be stack-allocated, + // receiver doesn't modify the data; in send(), there is buffering; + // in send_direct(), there is not. When switching from buffered to + // unbuffered mode, flush() must be called + template <class T> void send(int n, Pt<T> data); + void flush(); // goes with send(); + + // give: data must be dynamically allocated as a whole: the data + // will be deleted eventually, and should not be used by the caller + // beyond the call to give(). + template <class T> void give(int n, Pt<T> data); + + // third way to send (upcoming, in GF-0.8.??) is called "ask". + template <class T> void ask(int &n, Pt<T> &data, int factor, int min, int max); + +private: + void begin(int woutlet, P<Dim> dim, NumberTypeE nt=int32_e); + template <class T> void send_direct(int n, Pt<T> data); + void end() { + flush(); + for (uint32 i=0; i<inlets.size(); i++) inlets[i]->end(); + dim=0; + } +}; +\end class GridOutlet + +//**************************************************************** + +typedef struct BFObject BFObject; // Pd t_object or something + +// represents objects that have inlets/outlets +\class FObject < CObject +struct FObject : CObject { + BFObject *bself; // point to PD peer + uint64 total_time; + FObject() : bself(0), total_time(0) {} + const char *args() { + Ruby s=rb_funcall(rself,SI(args),0); + if (s==Qnil) return 0; + return rb_str_ptr(s); + } + \decl Ruby total_time_get(); + \decl Ruby total_time_set(Ruby x); + \decl void send_in (...); + \decl void send_out (...); + \decl void delete_m (); +}; +\end class FObject + +\class GridObject < FObject +struct GridObject : FObject { + std::vector<P<GridInlet> > in; + P<GridOutlet> out; + // Make sure you distinguish #close/#delete, and C++'s delete. The first + // two are quite equivalent and should never make an object "crashable". + // C++'s delete is called by Ruby's garbage collector or by PureData's delete. + GridObject() {} + ~GridObject() {check_magic();} + bool is_busy_except(P<GridInlet> gin) { + for (uint32 i=0; i<in.size(); i++) + if (in[i] && in[i]!=gin && in[i]->dim) return true; + return false; + } + \decl Ruby method_missing(...); + \decl Array inlet_dim(int inln); + \decl Symbol inlet_nt(int inln); + \decl void inlet_set_factor(int inln, int factor); + \decl void send_out_grid_begin(int outlet, Array dim, NumberTypeE nt=int32_e); + \decl void send_out_grid_flow (int outlet, String buf, NumberTypeE nt=int32_e); +}; +\end class GridObject + +uint64 gf_timeofday(); +extern "C" void Init_gridflow (); +void gfpost(const char *fmt, ...); +extern Numop *op_add,*op_sub,*op_mul,*op_div,*op_mod,*op_shl,*op_and,*op_put; + +#define INFO(OBJ) rb_str_ptr(rb_funcall(OBJ->rself,SI(info),0)) +//#define INFO(OBJ) "(bleh)" +#define NOTEMPTY(_a_) if (!(_a_)) RAISE("in [%s], '%s' is empty",INFO(this), #_a_); +#define SAME_TYPE(_a_,_b_) \ + if ((_a_)->nt != (_b_)->nt) RAISE("%s: same type please (%s has %s; %s has %s)", \ + INFO(this), \ + #_a_, number_type_table[(_a_)->nt].name, \ + #_b_, number_type_table[(_b_)->nt].name); +static void SAME_DIM(int n, P<Dim> a, int ai, P<Dim> b, int bi) { + if (ai+n > a->n) RAISE("left hand: not enough dimensions"); + if (bi+n > b->n) RAISE("right hand: not enough dimensions"); + for (int i=0; i<n; i++) { + if (a->v[ai+i] != b->v[bi+i]) { + RAISE("mismatch: left dim #%d is %d, right dim #%d is %d", + ai+i, a->v[ai+i], + bi+i, b->v[bi+i]);}}} + +// a stack for the profiler, etc. +#define GF_STACK_MAX 256 +struct GFStack { + struct GFStackFrame { + FObject *o; + void *bp; // a pointer into system stack + uint64 time; + }; // sizeof() == 16 (in 32-bit mode) + GFStackFrame s[GF_STACK_MAX]; + int n; + GFStack() { n = 0; } + void push (FObject *o) __attribute__((noinline)); + void pop () __attribute__((noinline)); +}; +extern GFStack gf_stack; +struct GFStackMarker { + int n; + bool flag; + GFStackMarker(FObject *o) { n = gf_stack.n; gf_stack.push(o); flag=true; } + ~GFStackMarker() { while (gf_stack.n != n) gf_stack.pop(); } + bool once () { + if (flag) { flag=false; return true; } else return false; + } +}; + +typedef GridObject Format; + +#endif // __GF_GRID_H diff --git a/externals/gridflow/base/main.c b/externals/gridflow/base/main.c new file mode 100644 index 00000000..5cdd92f4 --- /dev/null +++ b/externals/gridflow/base/main.c @@ -0,0 +1,647 @@ +/* + $Id: main.c,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdarg.h> + +#include "grid.h.fcs" +#include "../config.h" +#include <assert.h> +#include <limits.h> + +BuiltinSymbols bsym; +GFStack gf_stack; +Ruby mGridFlow; +Ruby cFObject; + +extern "C"{ +void rb_raise0( +const char *file, int line, const char *func, VALUE exc, const char *fmt, ...) { + va_list args; + char buf[BUFSIZ]; + va_start(args,fmt); + vsnprintf(buf, BUFSIZ, fmt, args); + buf[BUFSIZ-1]=0; + va_end(args); + VALUE e = rb_exc_new2(exc, buf); + char buf2[BUFSIZ]; + snprintf(buf2, BUFSIZ, "%s:%d:in `%s'", file, line, func); + buf2[BUFSIZ-1]=0; + VALUE ary = rb_funcall(e,SI(caller),0); + if (gf_stack.n) { + rb_funcall(ary,SI(unshift),2,rb_str_new2(buf2), + rb_str_new2(INFO(gf_stack.s[gf_stack.n-1].o))); + } else { + rb_funcall(ary,SI(unshift),1,rb_str_new2(buf2)); + } + rb_funcall(e,SI(set_backtrace),1,ary); + rb_exc_raise(e); +}}; + +Ruby rb_ary_fetch(Ruby rself, int i) { + Ruby argv[] = { INT2NUM(i) }; + return rb_ary_aref(COUNT(argv),argv,rself); +} + +//---------------------------------------------------------------- +// CObject + +static void CObject_mark (void *z) {} +void CObject_free (void *foo) { + CObject *self = (CObject *)foo; + self->check_magic(); + if (!self->rself) { + fprintf(stderr,"attempt to free object that has no rself\n"); + abort(); + } + self->rself = 0; /* paranoia */ + delete self; +} + +//---------------------------------------------------------------- +// Dim + +void Dim::check() { + if (n>MAX_DIMENSIONS) RAISE("too many dimensions"); + for (int i=0; i<n; i++) if (v[i]<0) RAISE("Dim: negative dimension"); +} + +// !@#$ big leak machine? +// returns a string like "Dim[240,320,3]" +char *Dim::to_s() { + // if you blow 256 chars it's your own fault + char buf[256]; + char *s = buf; + s += sprintf(s,"Dim["); + for(int i=0; i<n; i++) s += sprintf(s,"%s%d", ","+!i, v[i]); + s += sprintf(s,"]"); + return strdup(buf); +} + +//---------------------------------------------------------------- +\class FObject < CObject + +static void FObject_prepare_message(int &argc, Ruby *&argv, Ruby &sym, FObject *foo=0) { + if (argc<1) { + sym = bsym._bang; + } else if (argc>1 && !SYMBOL_P(*argv)) { + sym = bsym._list; + } else if (INTEGER_P(*argv)||FLOAT_P(*argv)) { + sym = bsym._float; + } else if (SYMBOL_P(*argv)) { + sym = *argv; + argc--, argv++; + } else if (argc==1 && TYPE(*argv)==T_ARRAY) { + sym = bsym._list; + argc = rb_ary_len(*argv); + argv = rb_ary_ptr(*argv); + } else { + RAISE("%s received bad message: argc=%d; argv[0]=%s",foo?INFO(foo):"", argc, + argc ? rb_str_ptr(rb_inspect(argv[0])) : ""); + } +} + +struct Helper { + int argc; + Ruby *argv; + FObject *self; + Ruby rself; + int n; // stack level +}; + +static Ruby GridFlow_handle_braces(Ruby rself, Ruby argv); + +// inlet #-1 is reserved for SystemInlet messages +// inlet #-2 is for inlet #0 messages that happen at start time +static void send_in_2 (Helper *h) { PROF(h->self) { + int argc = h->argc; + Ruby *argv = h->argv; + if (h->argc<1) RAISE("not enough args"); + int inlet = INT(argv[0]); + argc--, argv++; + Ruby foo; + if (argc==1 && TYPE(argv[0])==T_STRING /* && argv[0] =~ / / */) { + foo = rb_funcall(mGridFlow,SI(parse),1,argv[0]); + argc = rb_ary_len(foo); + argv = rb_ary_ptr(foo); + } + if (argc>1) { + foo = rb_ary_new4(argc,argv); + GridFlow_handle_braces(0,foo); + argc = rb_ary_len(foo); + argv = rb_ary_ptr(foo); + } + if (inlet==-2) { + Array init_messages = rb_ivar_get(h->rself,SI(@init_messages)); + rb_ary_push(init_messages, rb_ary_new4(argc,argv)); + inlet=0; + } + if (inlet<0 || inlet>9 /*|| inlet>real_inlet_max*/) + if (inlet!=-3 && inlet!=-1) RAISE("invalid inlet number: %d", inlet); + Ruby sym; + FObject_prepare_message(argc,argv,sym,h->self); +// if (rb_const_get(mGridFlow,SI(@verbose))==Qtrue) gfpost m.inspect + char buf[256]; + if (inlet==-1) sprintf(buf,"_sys_%s",rb_sym_name(sym)); + else sprintf(buf,"_%d_%s",inlet,rb_sym_name(sym)); + rb_funcall2(h->rself,rb_intern(buf),argc,argv); +} /* PROF */ } + +static void send_in_3 (Helper *h) { + while (gf_stack.n > h->n) gf_stack.pop(); +} + +\def void send_in (...) { + Helper h = {argc,argv,this,rself,gf_stack.n}; + rb_ensure( + (RMethod)send_in_2,(Ruby)&h, + (RMethod)send_in_3,(Ruby)&h); +} + +\def void send_out (...) { + int n=0; + if (argc<1) RAISE("not enough args"); + int outlet = INT(*argv); + if (outlet<0 || outlet>9 /*|| outlet>real_outlet_max*/) + RAISE("invalid outlet number: %d",outlet); + argc--, argv++; + Ruby sym; + FObject_prepare_message(argc,argv,sym,this); + Ruby noutlets2 = rb_ivar_get(rb_obj_class(rself),SYM2ID(SYM(@noutlets))); + if (TYPE(noutlets2)!=T_FIXNUM) { + IEVAL(rself,"STDERR.puts inspect"); + RAISE("don't know how many outlets this has"); + } + int noutlets = INT(noutlets2); + //if (outlet<0 || outlet>=noutlets) RAISE("outlet %d does not exist",outlet); + // was PROF(0) a hack because of exception-handling problems? + PROF(0) { + Ruby argv2[argc+2]; + for (int i=0; i<argc; i++) argv2[2+i] = argv[i]; + argv2[0] = INT2NUM(outlet); + argv2[1] = sym; + rb_funcall2(rself,SI(send_out2), argc+2, argv2); + + Ruby ary = rb_ivar_defined(rself,SYM2ID(bsym.iv_outlets)) ? + rb_ivar_get(rself,SYM2ID(bsym.iv_outlets)) : Qnil; + if (ary==Qnil) goto end; + if (TYPE(ary)!=T_ARRAY) RAISE("send_out: expected array"); + ary = rb_ary_fetch(ary,outlet); + if (ary==Qnil) goto end; + if (TYPE(ary)!=T_ARRAY) RAISE("send_out: expected array"); + n = rb_ary_len(ary); + + for (int i=0; i<n; i++) { + Ruby conn = rb_ary_fetch(ary,i); + Ruby rec = rb_ary_fetch(conn,0); + int inl = INT(rb_ary_fetch(conn,1)); + argv2[0] = INT2NUM(inl); + rb_funcall2(rec,SI(send_in),argc+2,argv2); + } + } /* PROF */ +end:; +} + +Ruby FObject_s_new(Ruby argc, Ruby *argv, Ruby qlass) { + Ruby allocator = rb_ivar_defined(qlass,SI(@allocator)) ? + rb_ivar_get(qlass,SI(@allocator)) : Qnil; + FObject *self; + if (allocator==Qnil) { + // this is a pure-ruby FObject/GridObject + // !@#$ GridObject is in FObject constructor (ugly) + self = new GridObject; + } else { + // this is a C++ FObject/GridObject + void*(*alloc)() = (void*(*)())FIX2PTR(void,allocator); + self = (FObject *)alloc(); + } + self->check_magic(); + Ruby keep = rb_ivar_get(mGridFlow, SI(@fobjects)); + self->bself = 0; + Ruby rself = Data_Wrap_Struct(qlass, CObject_mark, CObject_free, self); + self->rself = rself; + rb_hash_aset(keep,rself,Qtrue); // prevent sweeping + rb_funcall2(rself,SI(initialize),argc,argv); + return rself; +} + +Ruby FObject_s_install(Ruby rself, Ruby name, Ruby inlets2, Ruby outlets2) { + int inlets, outlets; + Ruby name2; + if (SYMBOL_P(name)) { + name2 = rb_funcall(name,SI(to_str),0); + } else if (TYPE(name) == T_STRING) { + name2 = rb_funcall(name,SI(dup),0); + } else { + RAISE("expect symbol or string"); + } + inlets = INT(inlets2); if ( inlets<0 || inlets>9) RAISE("..."); + outlets = INT(outlets2); if (outlets<0 || outlets>9) RAISE("..."); + rb_ivar_set(rself,SI(@ninlets),INT2NUM(inlets)); + rb_ivar_set(rself,SI(@noutlets),INT2NUM(outlets)); + rb_ivar_set(rself,SI(@foreign_name),name2); + rb_hash_aset(rb_ivar_get(mGridFlow,SI(@fclasses)), name2, rself); + rb_funcall(rself, SI(install2), 1, name2); + return Qnil; +} + +\def Ruby total_time_get () {return gf_ull2num(total_time);} + +\def Ruby total_time_set (Ruby x) { + if (argc<1) RAISE("muh"); + total_time = TO(uint64,x); + return argv[0]; +} + +\def void delete_m () { + Ruby keep = rb_ivar_get(mGridFlow, SI(@fobjects)); + rb_funcall(keep,SI(delete),1,rself); +} + +\classinfo +\end class FObject + +/* ---------------------------------------------------------------- */ +/* C++<->Ruby bridge for classes/functions in base/number.c */ + +static Ruby String_swap32_f (Ruby rself) { + int n = rb_str_len(rself)/4; + swap32(n,Pt<uint32>((uint32 *)rb_str_ptr(rself),n)); + return rself; +} + +static Ruby String_swap16_f (Ruby rself) { + int n = rb_str_len(rself)/2; + swap16(n,Pt<uint16>((uint16 *)rb_str_ptr(rself),n)); + return rself; +} + +NumberTypeE NumberTypeE_find (Ruby sym) { + if (TYPE(sym)!=T_SYMBOL) RAISE("expected symbol (not %s)", + rb_str_ptr(rb_inspect(rb_obj_class(sym)))); + Ruby nt_dict = rb_ivar_get(mGridFlow,SI(@number_type_dict)); + Ruby v = rb_hash_aref(nt_dict,sym); + if (v!=Qnil) return FIX2PTR(NumberType,v)->index; + RAISE("unknown number type \"%s\"", rb_sym_name(sym)); +} + +/* **************************************************************** */ +\class BitPacking < CObject + +\def void initialize(Ruby foo1, Ruby foo2, Ruby foo3) {} + +// !@#$ doesn't support number types +\def String pack2 (String ins, String outs=Qnil) { + int n = rb_str_len(ins) / sizeof(int32) / size; + Pt<int32> in = Pt<int32>((int32 *)rb_str_ptr(ins),rb_str_len(ins)); + int bytes2 = n*bytes; + Ruby out = outs!=Qnil ? rb_str_resize(outs,bytes2) : rb_str_new("",bytes2); + rb_str_modify(out); + pack(n,Pt<int32>(in,n),Pt<uint8>((uint8 *)rb_str_ptr(out),bytes2)); + return out; +} + +// !@#$ doesn't support number types +\def String unpack2 (String ins, String outs=Qnil) { + int n = rb_str_len(argv[0]) / bytes; + Pt<uint8> in = Pt<uint8>((uint8 *)rb_str_ptr(ins),rb_str_len(ins)); + int bytes2 = n*size*sizeof(int32); + Ruby out = outs!=Qnil ? rb_str_resize(outs,bytes2) : rb_str_new("",bytes2); + rb_str_modify(out); + unpack(n,Pt<uint8>((uint8 *)in,bytes2),Pt<int32>((int32 *)rb_str_ptr(out),n)); + return out; +} + +static Ruby BitPacking_s_new(Ruby argc, Ruby *argv, Ruby qlass) { + Ruby keep = rb_ivar_get(mGridFlow, rb_intern("@fobjects")); + if (argc!=3) RAISE("bad args"); + if (TYPE(argv[2])!=T_ARRAY) RAISE("bad mask"); + int endian = INT(argv[0]); + int bytes = INT(argv[1]); + Ruby *masks = rb_ary_ptr(argv[2]); + uint32 masks2[4]; + int size = rb_ary_len(argv[2]); + if (size<1) RAISE("not enough masks"); + if (size>4) RAISE("too many masks (%d)",size); + for (int i=0; i<size; i++) masks2[i] = NUM2UINT(masks[i]); + BitPacking *self = new BitPacking(endian,bytes,size,masks2); + Ruby rself = Data_Wrap_Struct(qlass, 0, CObject_free, self); + self->rself = rself; + rb_hash_aset(keep,rself,Qtrue); // prevent sweeping (leak) (!@#$ WHAT???) + rb_funcall2(rself,SI(initialize),argc,argv); + return rself; +} + +\classinfo +\end class BitPacking + +void gfpost(const char *fmt, ...) { + va_list args; + int length; + va_start(args,fmt); + const int n=256; + char post_s[n]; + length = vsnprintf(post_s,n,fmt,args); + if (length<0 || length>=n) sprintf(post_s+n-6,"[...]"); /* safety */ + va_end(args); + rb_funcall(mGridFlow,SI(gfpost2),2,rb_str_new2(fmt),rb_str_new2(post_s)); +} + +void define_many_methods(Ruby rself, int n, MethodDecl *methods) { + for (int i=0; i<n; i++) { + MethodDecl *md = &methods[i]; + char *buf = strdup(md->selector); + if (strlen(buf)>2 && strcmp(buf+strlen(buf)-2,"_m")==0) + buf[strlen(buf)-2]=0; + rb_define_method(rself,buf,(RMethod)md->method,-1); + rb_enable_super(rself,buf); + free(buf); + } +} + +static Ruby GridFlow_fclass_install(Ruby rself_, Ruby fc_, Ruby super) { + FClass *fc = FIX2PTR(FClass,fc_); + Ruby rself = super!=Qnil ? + rb_define_class_under(mGridFlow, fc->name, super) : + rb_funcall(mGridFlow,SI(const_get),1,rb_str_new2(fc->name)); + define_many_methods(rself,fc->methodsn,fc->methods); + rb_ivar_set(rself,SI(@allocator),PTR2FIX((void*)(fc->allocator))); //#!@$?? + if (fc->startup) fc->startup(rself); + return Qnil; +} + +//---------------------------------------------------------------- +// GridFlow.class +//\class GridFlow_s < patate + +typedef void (*Callback)(void*); +static Ruby GridFlow_exec (Ruby rself, Ruby data, Ruby func) { + void *data2 = FIX2PTR(void,data); + Callback func2 = (Callback) FIX2PTR(void,func); + func2(data2); + return Qnil; +} + +static Ruby GridFlow_get_id (Ruby rself, Ruby arg) { + fprintf(stderr,"%ld\n",arg); + return INT2NUM((int)arg); +} + +Ruby GridFlow_rdtsc (Ruby rself) { return gf_ull2num(rdtsc()); } + +/* This code handles nested lists because PureData (0.38) doesn't do it */ +static Ruby GridFlow_handle_braces(Ruby rself, Ruby argv) { + int stack[16]; + int stackn=0; + Ruby *av = rb_ary_ptr(argv); + int ac = rb_ary_len(argv); + int j=0; + for (int i=0; i<ac; ) { + int close=0; + if (SYMBOL_P(av[i])) { + const char *s = rb_sym_name(av[i]); + while (*s=='(' || *s=='{') { + if (stackn==16) RAISE("too many nested lists (>16)"); + stack[stackn++]=j; + s++; + } + const char *se = s+strlen(s); + while (se[-1]==')' || se[-1]=='}') { se--; close++; } + if (s!=se) { + Ruby u = rb_str_new(s,se-s); + av[j++] = rb_funcall(rself,SI(FloatOrSymbol),1,u); + } + } else { + av[j++]=av[i]; + } + i++; + while (close--) { + if (!stackn) RAISE("unbalanced '}' or ')'",av[i]); + Ruby a2 = rb_ary_new(); + int j2 = stack[--stackn]; + for (int k=j2; k<j; k++) rb_ary_push(a2,av[k]); + j=j2; + av[j++] = a2; + } + } + if (stackn) RAISE("unbalanced '{' or '(' (stackn=%d)",stackn); + RARRAY(argv)->len = j; + return rself; +} + +/* ---------------------------------------------------------------- */ + +static uint32 memcpy_calls = 0; +static uint64 memcpy_bytes = 0; +static uint64 memcpy_time = 0; +static uint32 malloc_calls = 0; /* only new not delete */ +static uint64 malloc_bytes = 0; /* only new not delete */ +static uint64 malloc_time = 0; /* in cpu ticks */ + +// don't touch. +static void gfmemcopy32(int32 *as, int32 *bs, int n) { + int32 ba = bs-as; +#define FOO(I) as[I] = (as+ba)[I]; + UNROLL_8(FOO,n,as) +#undef FOO + +} + +void gfmemcopy(uint8 *out, const uint8 *in, int n) { + uint64 t = rdtsc(); + memcpy_calls++; + memcpy_bytes+=n; + for (; n>16; in+=16, out+=16, n-=16) { + ((int32*)out)[0] = ((int32*)in)[0]; + ((int32*)out)[1] = ((int32*)in)[1]; + ((int32*)out)[2] = ((int32*)in)[2]; + ((int32*)out)[3] = ((int32*)in)[3]; + } + for (; n>4; in+=4, out+=4, n-=4) { *(int32*)out = *(int32*)in; } + for (; n; in++, out++, n--) { *out = *in; } + t=rdtsc()-t; + memcpy_time+=t; +} + +extern "C" { +void *gfmalloc(size_t n) { + uint64 t = rdtsc(); + void *p = malloc(n); + long align = (long)p & 7; + if (align) fprintf(stderr,"malloc alignment = %ld mod 8\n",align); + t=rdtsc()-t; + malloc_time+=t; + malloc_calls++; + malloc_bytes+=n; + return p; +} +void gffree(void *p) { + uint64 t = rdtsc(); + free(p); + t=rdtsc()-t; + malloc_time+=t; +}}; + +Ruby GridFlow_memcpy_calls (Ruby rself) { return LONG2NUM(memcpy_calls); } +Ruby GridFlow_memcpy_bytes (Ruby rself) { return gf_ull2num(memcpy_bytes); } +Ruby GridFlow_memcpy_time (Ruby rself) { return gf_ull2num(memcpy_time); } +Ruby GridFlow_malloc_calls (Ruby rself) { return LONG2NUM(malloc_calls); } +Ruby GridFlow_malloc_bytes (Ruby rself) { return gf_ull2num(malloc_bytes); } +Ruby GridFlow_malloc_time (Ruby rself) { return gf_ull2num(malloc_time); } + +Ruby GridFlow_profiler_reset2 (Ruby rself) { + memcpy_calls = memcpy_bytes = memcpy_time = 0; + malloc_calls = malloc_bytes = malloc_time = 0; + return Qnil; +} + +/* ---------------------------------------------------------------- */ + +void startup_number(); +void startup_grid(); +void startup_flow_objects(); +void startup_flow_objects_for_image(); +void startup_flow_objects_for_matrix(); + +Ruby cFormat; + +#define SDEF(_class_,_name_,_argc_) \ + rb_define_singleton_method(c##_class_,#_name_,(RMethod)_class_##_s_##_name_,_argc_) +#define SDEF2(_name1_,_name2_,_argc_) \ + rb_define_singleton_method(mGridFlow,_name1_,(RMethod)_name2_,_argc_) + +STARTUP_LIST(void) + +// Ruby's entrypoint. +void Init_gridflow () { +#define FOO(_sym_,_name_) bsym._sym_ = ID2SYM(rb_intern(_name_)); +BUILTIN_SYMBOLS(FOO) +#undef FOO + signal(11,SIG_DFL); // paranoia + mGridFlow = EVAL("module GridFlow; CObject = ::Object; " + "class<<self; attr_reader :bridge_name; end; " + "def post_string(s) STDERR.puts s end; " + "self end"); + SDEF2("exec",GridFlow_exec,2); + SDEF2("get_id",GridFlow_get_id,1); + SDEF2("rdtsc",GridFlow_rdtsc,0); + SDEF2("profiler_reset2",GridFlow_profiler_reset2,0); + SDEF2("memcpy_calls",GridFlow_memcpy_calls,0); + SDEF2("memcpy_bytes",GridFlow_memcpy_bytes,0); + SDEF2("memcpy_time", GridFlow_memcpy_time,0); + SDEF2("malloc_calls",GridFlow_malloc_calls,0); + SDEF2("malloc_bytes",GridFlow_malloc_bytes,0); + SDEF2("malloc_time", GridFlow_malloc_time,0); + SDEF2("handle_braces!",GridFlow_handle_braces,1); + SDEF2("fclass_install",GridFlow_fclass_install,2); + +//#define FOO(A) fprintf(stderr,"sizeof("#A")=%d\n",sizeof(A)); +//FOO(Dim) FOO(BitPacking) FOO(GridHandler) FOO(GridInlet) FOO(GridOutlet) FOO(GridObject) +//#undef FOO + + rb_ivar_set(mGridFlow, SI(@fobjects), rb_hash_new()); + rb_ivar_set(mGridFlow, SI(@fclasses), rb_hash_new()); + rb_ivar_set(mGridFlow, SI(@bsym), PTR2FIX(&bsym)); + rb_define_const(mGridFlow, "GF_VERSION", rb_str_new2(GF_VERSION)); + rb_define_const(mGridFlow, "GF_COMPILE_TIME", rb_str_new2(GF_COMPILE_TIME)); + + cFObject = rb_define_class_under(mGridFlow, "FObject", rb_cObject); + EVAL( +\ruby + module GridFlow + class FObject + def send_out2(*) end + def self.install2(*) end + def self.add_creator(name) + name=name.to_str.dup + GridFlow.fclasses[name]=self + GridFlow.add_creator_2 name end + end + end +\end ruby +); + define_many_methods(cFObject,COUNT(FObject_methods),FObject_methods); + SDEF(FObject, install, 3); + SDEF(FObject, new, -1); + ID gbi = SI(gf_bridge_init); + if (rb_respond_to(rb_cData,gbi)) rb_funcall(rb_cData,gbi,0); + Ruby cBitPacking = + rb_define_class_under(mGridFlow, "BitPacking", rb_cObject); + define_many_methods(cBitPacking, + ciBitPacking.methodsn, + ciBitPacking.methods); + SDEF(BitPacking,new,-1); + rb_define_method(rb_cString, "swap32!", (RMethod)String_swap32_f, 0); + rb_define_method(rb_cString, "swap16!", (RMethod)String_swap16_f, 0); + + startup_number(); + startup_grid(); + startup_flow_objects(); + startup_flow_objects_for_image(); + startup_flow_objects_for_matrix(); + if (!EVAL("begin require 'gridflow/base/main.rb'; true\n" + "rescue Exception => e; " + "STDERR.puts \"can't load: #{$!}\n" + "backtrace: #{$!.backtrace.join\"\n\"}\n" + "$: = #{$:.inspect}\"\n; false end")) return; + cFormat = EVAL("GridFlow::Format"); + STARTUP_LIST() + EVAL("h=GridFlow.fclasses; h['#io:window'] = h['#io:quartz']||h['#io:x11']||h['#io:sdl']"); + EVAL("GridFlow.load_user_config"); + signal(11,SIG_DFL); // paranoia +} + +void GFStack::push (FObject *o) { + void *bp = &o; // really. just finding our position on the stack. + if (n>=GF_STACK_MAX) + RAISE("stack overflow (maximum %d FObject activations at once)", GF_STACK_MAX); + uint64 t = rdtsc(); + if (n) s[n-1].time = t - s[n-1].time; + s[n].o = o; + s[n].bp = bp; + s[n].time = t; + n++; +} + +void GFStack::pop () { + uint64 t = rdtsc(); + if (!n) RAISE("stack underflow (WHAT?)"); + n--; + if (s[n].o) s[n].o->total_time += t - s[n].time; + if (n) s[n-1].time = t - s[n-1].time; +} + +uint64 gf_timeofday () { + timeval t; + gettimeofday(&t,0); + return t.tv_sec*1000000+t.tv_usec; +} diff --git a/externals/gridflow/base/main.rb b/externals/gridflow/base/main.rb new file mode 100644 index 00000000..82976fb6 --- /dev/null +++ b/externals/gridflow/base/main.rb @@ -0,0 +1,369 @@ +=begin + $Id: main.rb,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002 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 + +# ENV["RUBY_VERBOSE_GC"]="yes" + +# this file gets loaded by main.c upon startup +# module GridFlow is supposed to be created by main.c +# this includes GridFlow.post_string(s) + +# because Ruby1.6 has no #object_id and Ruby1.8 warns on #id +unless Object.instance_methods(true).include? "object_id" + class Object; alias object_id id end +end + +# in case of bug in Ruby ("Error: Success") +module Errno; class E000 < StandardError; end; end + +#$post_log = File.open "/tmp/gridflow.log", "w" +$post_log = nil + +class Array + def split(elem) + r=[] + j=0 + for i in 0...length + (r<<self[j,i-j]; j=i+1) if self[i]==elem + end + r<<self[j,length-j] + end +end + +module GridFlow #------------------ + +def self.post(s,*a) + post_string(sprintf("%s"+s,post_header,*a)) + ($post_log << sprintf(s,*a); $post_log.flush) if $post_log +end + +class<<self + attr_accessor :data_path + attr_accessor :post_header + attr_accessor :verbose + attr_reader :fobjects + attr_reader :fclasses + attr_reader :cpu_hertz + attr_reader :subprocesses + attr_reader :bridge_name + alias gfpost post +end + +@subprocesses={} +@verbose=false +@data_path=[] +if GridFlow.respond_to? :config then + @data_path << GridFlow.config["PUREDATA_PATH"]+"/extra/gridflow/images" +end + +def self.hunt_zombies + #STDERR.puts "GridFlow.hunt_zombies" + # the $$ value is bogus + begin + died = [] + subprocesses.each {|x,v| + Process.waitpid2(x,Process::WNOHANG) and died<<x + } + rescue Errno::ECHILD + end + #STDERR.puts died.inspect + died.each {|x| subprocesses.delete x } +end + +def self.packstring_for_nt(nt) + case nt + when :u, :u8, :uint8; "C*" + when :s, :i16, :int16; "s*" + when :i, :i32, :int32; "l*" + when :f, :f32, :float32; "f*" + when :d, :f64, :float64; "d*" + else raise "no decoder for #{nt.inspect}" + end +end + +self.post_header = "[gf] " + +def self.gfpost2(fmt,s); post("%s",s) end + +if GridFlow.bridge_name then + post "This is GridFlow #{GridFlow::GF_VERSION} within Ruby version #{RUBY_VERSION}" + post "base/main.c was compiled on #{GridFlow::GF_COMPILE_TIME}" + post "Please use at least 1.6.6 if you plan to use sockets" if RUBY_VERSION<"1.6.6" +end + +if not GridFlow.bridge_name then + require "gridflow/bridge/placebo" +end + +Brace1 = "{".intern +Brace2 = "}".intern +Paren1 = "(".intern +Paren2 = ")".intern + +def self.parse(m) + m = m.gsub(/(\{|\})/," \\1 ").split(/\s+/) + m.map! {|x| case x + when Integer, Symbol; x + when /^[+\-]?[0-9]+$/; x.to_i + when String; x.intern + end + } + m +end + +def self.stringify_list(argv) + argv.map {|x| stringify x }.join(" ") +end + +def self.stringify(arg) + case arg + when Integer, Float, Symbol; arg.to_s + when Array; "{#{stringify_list arg}}" + end +end + +::Object.module_eval do def FloatOrSymbol(x) Float(x) rescue x.intern end end + +# adding some functionality to that: +class FObject + @broken_ok = false + @do_loadbangs = true + class<<self + # global + attr_accessor :broken_ok + # per-class + attr_reader :ninlets + attr_reader :noutlets + attr_accessor :do_loadbangs + attr_accessor :comment + def foreign_name; @foreign_name if defined? @foreign_name end + end + def post(*a) GridFlow.post(*a) end + def self.subclass(*args,&b) + qlass = Class.new self + qlass.install(*args) + qlass.module_eval(&b) + end + alias :total_time :total_time_get + alias :total_time= :total_time_set + attr_writer :args # String + attr_accessor :argv # Array + attr_reader :outlets + attr_accessor :parent_patcher + attr_accessor :properties + attr_accessor :classname + def initialize2; end + def args + if defined? @args + @args + else + "[#{self.class} ...]" + end + end + alias info args + def connect outlet, object, inlet + @outlets ||= [] + @outlets[outlet] ||= [] + @outlets[outlet].push [object, inlet] + end + def self.name_lookup sym + qlasses = GridFlow.fclasses + qlass = qlasses[sym.to_s] + if not qlass + return qlasses['broken'] if @broken_ok + raise "object class '#{sym}' not found" + end + qlass + end + def self.[](*m) + o=nil + if m.length==1 and m[0] =~ / / + o="[#{m[0]}]" + m=GridFlow.parse(m[0]) + else + o=m.inspect + end + GridFlow.handle_braces!(m) + ms = m.split ','.intern + m = ms.shift + qlass = m.shift + qlassname = qlass.to_s + qlass = name_lookup qlass.to_s unless Class===qlass + r = qlass.new(*m) + r.classname = qlassname + GridFlow.post "%s",r.args if GridFlow.verbose + for x in ms do r.send_in(-2, *x) end if FObject.do_loadbangs + r + end + def inspect + if args then "#<#{self.class} #{args}>" else super end + end + def initialize(*argv) + s = GridFlow.stringify_list argv + @argv = argv + @args = "[" + @args << (self.class.foreign_name || self.to_s) + @args << " " if s.length>0 + @args << s << "]" + @parent_patcher = nil + @properties = {} + @init_messages = [] + end +end + +class FPatcher < FObject + class << self + attr_reader :fobjects + attr_reader :wires + end + def initialize(*) + super + fobjects = self.class.fobjects + wires = self.class.wires + @fobjects = fobjects.map {|x| if String===x then FObject[x] else x.call end } + @inlets = [] + @ninlets = self.class.ninlets or raise "oops" + i=0 + @fobjects << self + while i<wires.length do + a,b,c,d = wires[i,4] + if a==-1 then + a=self + @inlets[b]||=[] + @inlets[b] << [@fobjects[c],d] + else + if c==-1 then + @fobjects[a].connect b,self,d+@ninlets + else + @fobjects[a].connect b,@fobjects[c],d + end + end + i+=4 + end + end + def method_missing(sym,*args) + sym=sym.to_s + if sym =~ /^_(\d)_(.*)/ then + inl = Integer $1 + sym = $2.intern + if inl<@ninlets then + raise "#{inspect} has not @inlets[#{inl}]" if not @inlets[inl] + for x in @inlets[inl] do + x[0].send_in x[1],sym,*args end + else + send_out(inl-@ninlets,sym,*args) + end + else super end + end +end + +def GridFlow.estimate_cpu_clock + u0,t0=GridFlow.rdtsc,Time.new.to_f; sleep 0.01 + u1,t1=GridFlow.rdtsc,Time.new.to_f; (u1-u0)/(t1-t0) +end + +begin + @cpu_hertz = (0...3).map { + GridFlow.estimate_cpu_clock + }.sort[1] # median of three tries +rescue + GridFlow.post $! +end + +def GridFlow.find_file s + s=s.to_s + if s==File.basename(s) then + dir = GridFlow.data_path.find {|x| File.exist? "#{x}/#{s}" } + if dir then "#{dir}/#{s}" else s end + elsif GridFlow.respond_to? :find_file_2 + GridFlow.find_file_2 s + else + s + end +end + +def GridFlow.macerr(i) + begin + f=File.open("/System/Library/Frameworks/CoreServices.framework/"+ + "Versions/A/Frameworks/CarbonCore.framework/Versions/A/Headers/"+ + "MacErrors.h") + while f.gets + m = /^\s*(\w+)\s*=\s*(-\d+),\s*\/\*\s*(.*)\s*\*\/$/.match $_ + next if not m + if m[2].to_i == i then return "#{m[2]}: \"#{m[3]}\"" end + end + return "no error message available for this error number" + rescue FileError + return "Can't find Apple's precious copyrighted list of error messages on this system." + ensure + f.close if f + end +end + +end # module GridFlow + +class IO + def nonblock= flag + bit = Fcntl::O_NONBLOCK + state = fcntl(Fcntl::F_GETFL, 0) + fcntl(Fcntl::F_SETFL, (state & ~bit) | + (if flag; bit else 0 end)) + end +end + +def protect + yield +rescue Exception => e + STDERR.puts "#{e.class}: #{e}" + STDERR.puts e.backtrace +end + +def GridFlow.load_user_config + require "gridflow/bridge/puredata.rb" if GridFlow.bridge_name == "puredata" + user_config_file = ENV["HOME"] + "/.gridflow_startup" + begin + load user_config_file if File.exist? user_config_file + rescue Exception => e + GridFlow.post "#{e.class}: #{e}:\n" + e.backtrace.join("\n") + GridFlow.post "while loading ~/.gridflow_startup" + end +end + +require "gridflow/base/flow_objects.rb" +require "gridflow/format/main.rb" + +%w( + # #for #finished #type #dim #transpose #perspective #store #outer + #grade #redim #import #export #export_list #cast + #scale_by #downscale_by #draw_polygon #draw_image #layer + #print #pack #export_symbol #rotate + #in #out +).each {|k| + GridFlow::FObject.name_lookup(k).add_creator k.gsub(/#/,"@") +} + +END { + GridFlow.fobjects.each {|k,v| k.delete if k.respond_to? :delete } + GridFlow.fobjects.clear + GC.start +} + diff --git a/externals/gridflow/base/number.c b/externals/gridflow/base/number.c new file mode 100644 index 00000000..c483cf0b --- /dev/null +++ b/externals/gridflow/base/number.c @@ -0,0 +1,365 @@ +/* + $Id: number.c,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include "grid.h.fcs" +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> + +#ifdef PASS1 +NumberType number_type_table[] = { +#define FOO(_sym_,_size_,_flags_,args...) NumberType( #_sym_, _size_, _flags_, args ), +NUMBER_TYPES(FOO) +#undef FOO +}; +const long number_type_table_n = COUNT(number_type_table); +#endif + +// those are bogus class-templates in the sense that you don't create +// objects from those, you just call static functions. The same kind +// of pattern is present in STL to overcome some limitations of C++. + +template <class T> class Op { +public: + // I call abort() on those because I can't say they're purevirtual. + static T f(T a, T b) {abort();} + static bool is_neutral(T x, LeftRight side) {assert(!"Op::is_neutral called?");} + static bool is_absorbent(T x, LeftRight side) {assert(!"Op::is_absorbent called?");} +}; + +template <class O> class OpLoops { +public: + template <class T> static void op_map (int n, T *as, T b) { + if (!n) return; +#define FOO(I) as[I]=O::f(as[I],b); + UNROLL_8(FOO,n,as) +#undef FOO + } + template <class T> static void op_zip (int n, T *as, T *bs) { + if (!n) return; + int ba=bs-as; // really! +#define FOO(I) as[I]=O::f(as[I],as[ba+I]); + UNROLL_8(FOO,n,as) +#undef FOO + } + // disabled + template <class T> static void op_zip2 (int n, T *as, T *bs, T *cs) { + if (!n) return; + int ba=bs-as, ca=cs-as; +#define FOO(I) as[ca+I]=O::f(as[I],as[ba+I]); + UNROLL_8(FOO,n,as) +#undef FOO + } +#define W(i) as[i]=O::f(as[i],bs[i]); +#define Z(i,j) as[i]=O::f(O::f(O::f(O::f(as[i],bs[i]),bs[i+j]),bs[i+j+j]),bs[i+j+j+j]); + template <class T> static void op_fold (int an, int n, T *as, T *bs) { + switch (an) { + case 1: for (; (n&3)!=0; bs++, n--) W(0); + for (; n; bs+=4, n-=4) { Z(0,1); } break; + case 2: for (; (n&3)!=0; bs+=2, n--) { W(0); W(1); } + for (; n; bs+=8, n-=4) { Z(0,2); Z(1,2); } break; + case 3: for (; (n&3)!=0; bs+=3, n--) { W(0); W(1); W(2); } + for (; n; bs+=12, n-=4) { Z(0,3); Z(1,3); Z(2,3); } break; + case 4: for (; (n&3)!=0; bs+=4, n--) { W(0); W(1); W(2); W(3); } + for (; n; bs+=16, n-=4) { Z(0,4); Z(1,4); Z(2,4); Z(3,4); } break; + default:for (; n--; ) { + int i=0; + for (; i<(an&-4); i+=4, bs+=4) { + as[i+0]=O::f(as[i+0],bs[0]); + as[i+1]=O::f(as[i+1],bs[1]); + as[i+2]=O::f(as[i+2],bs[2]); + as[i+3]=O::f(as[i+3],bs[3]); + } + for (; i<an; i++, bs++) as[i] = O::f(as[i],*bs); + } + } + } + template <class T> static void op_scan (int an, int n, T *as, T *bs) { + for (; n--; as=bs-an) { + for (int i=0; i<an; i++, as++, bs++) *bs=O::f(*as,*bs); + } + } +}; + +template <class T> +static void quick_mod_map (int n, T *as, T b) { + if (!b) return; +#define FOO(I) as[I]=mod(as[I],b); + UNROLL_8(FOO,n,as) +#undef FOO +} + +template <class T> static void quick_ign_map (int n, T *as, T b) {} +template <class T> static void quick_ign_zip (int n, T *as, T *bs) {} +template <class T> static void quick_put_map (int n, T *as, T b) { +#define FOO(I) as[I]=b; + UNROLL_8(FOO,n,as) +#undef FOO +} + +#ifdef PASS1 +void quick_put_map (int n, int16 *as, int16 b) { + if (n&1!=0 && (long)as&4!=0) { *as++=b; n--; } + quick_put_map (n>>1, (int32 *)as, (int32)(b<<16)+b); + if (n&1!=0) *as++=b; +} +void quick_put_map (int n, uint8 *as, uint8 b) { + while (n&3!=0 && (long)as&4!=0) { *as++=b; n--; } + int32 c=(b<<8)+b; c+=c<<16; + quick_put_map (n>>2, (int32 *)as, c); + while (n&3!=0) *as++=b; +} +#endif +template <class T> static void quick_put_zip (int n, T *as, T *bs) { + gfmemcopy((uint8 *)as, (uint8 *)bs, n*sizeof(T)); +} + +// classic two-input operator +#define DEF_OP(op,expr,neutral,absorbent) \ + template <class T> class Y##op : Op<T> { public: \ + inline static T f(T a, T b) { return expr; } \ + inline static bool is_neutral (T x, LeftRight side) { return neutral; } \ + inline static bool is_absorbent(T x, LeftRight side) { return absorbent; } }; +#define DEF_OPFT(op,expr,neutral,absorbent,T) \ + template <> class Y##op<T> : Op<T> { public: \ + inline static T f(T a, T b) { return expr; } \ + inline static bool is_neutral (T x, LeftRight side) { return neutral; } \ + inline static bool is_absorbent(T x, LeftRight side) { return absorbent; } }; \ +// this macro is for operators that have different code for the float version +#define DEF_OPF(op,expr,expr2,neutral,absorbent) \ + DEF_OP( op,expr, neutral,absorbent) \ + DEF_OPFT(op,expr2,neutral,absorbent,float32) \ + DEF_OPFT(op,expr2,neutral,absorbent,float64) + +#define DECL_OPON(base,op,T) NumopOn<T>( \ + &base<Y##op<T> >::op_map, &base<Y##op<T> >::op_zip, \ + &base<Y##op<T> >::op_fold, &base<Y##op<T> >::op_scan, \ + &Y##op<T>::is_neutral, &Y##op<T>::is_absorbent) +#define DECL_OPON_NOFOLD(base,op,T) NumopOn<T>( \ + &base<Y##op<T> >::op_map, &base<Y##op<T> >::op_zip, 0,0, \ + &Y##op<T>::is_neutral, &Y##op<T>::is_absorbent) +#define DECL_OP(op,sym,flags) Numop(0, sym, \ + DECL_OPON(OpLoops,op,uint8), DECL_OPON(OpLoops,op,int16), \ + DECL_OPON(OpLoops,op,int32) NONLITE(, DECL_OPON(OpLoops,op,int64), \ + DECL_OPON(OpLoops,op,float32), DECL_OPON(OpLoops,op,float64)), flags) +#define DECL_OP_NOFLOAT(op,sym,flags) Numop(0, sym, \ + DECL_OPON(OpLoops,op,uint8), DECL_OPON(OpLoops,op,int16), \ + DECL_OPON(OpLoops,op,int32) NONLITE(, DECL_OPON(OpLoops,op,int64), \ + NumopOn<float32>(0,0,0,0,0,0), NumopOn<float64>(0,0,0,0,0,0)), flags) +#define DECL_OP_NOFOLD(op,sym,flags) Numop(0, sym, \ + DECL_OPON_NOFOLD(OpLoops,op,uint8), DECL_OPON_NOFOLD(OpLoops,op,int16), \ + DECL_OPON_NOFOLD(OpLoops,op,int32) NONLITE(, DECL_OPON_NOFOLD(OpLoops,op,int64), \ + DECL_OPON_NOFOLD(OpLoops,op,float32), DECL_OPON_NOFOLD(OpLoops,op,float64)), flags) + +template <class T> static inline T gf_floor (T a) { + return (T) floor((double)a); } +template <class T> static inline T gf_trunc (T a) { + return (T) floor(abs((double)a)) * (a<0?-1:1); } + +/* +uint8 clipadd(uint8 a, uint8 b) { int32 c=a+b; return c<0?0:c>255?255:c; } +int16 clipadd(int16 a, int16 b) { int32 c=a+b; return c<-0x8000?-0x8000:c>0x7fff?0x7fff:c; } +int32 clipadd(int32 a, int32 b) { int64 c=a+b; return c<-0x80000000?-0x80000000:c>0x7fffffff?0x7fffffff:c; } +int64 clipadd(int64 a, int64 b) { int64 c=(a>>1)+(b>>1)+(a&b&1); + return c<(nt_smallest(0LL)/2?nt_smallest(0LL):c>nt_greatest(0LL)/2?nt_greatest(0LL):a+b; } +uint8 clipsub(uint8 a, uint8 b) { int32 c=a-b; return c<0?0:c>255?255:c; } +int16 clipsub(int16 a, int16 b) { int32 c=a-b; return c<-0x8000?-0x8000:c>0x7fff?0x7fff:c; } +int32 clipsub(int32 a, int32 b) { int64 c=a-b; return c<-0x80000000?-0x80000000:c>0x7fffffff?0x7fffffff:c; } +int64 clipsub(int64 a, int64 b) { int64 c=(a>>1)-(b>>1); //??? + return c<(nt_smallest(0LL)/2?nt_smallest(0LL):c>nt_greatest(0LL)/2?nt_greatest(0LL):a-b; } +*/ + +#ifdef PASS1 +DEF_OP(ignore, a, side==at_right, side==at_left) +DEF_OP(put, b, side==at_left, side==at_right) +DEF_OP(add, a+b, x==0, false) +DEF_OP(sub, a-b, side==at_right && x==0, false) +DEF_OP(bus, b-a, side==at_left && x==0, false) +DEF_OP(mul, a*b, x==1, x==0) +DEF_OP(mulshr8, ((int32)a*(int32)b)>>8, (int64)x==256, x==0) //!@#$ bug with int64 +DEF_OP(div, b==0 ? 0 : a/b, side==at_right && x==1, false) +DEF_OP(div2, b==0 ? 0 : div2(a,b), side==at_right && x==1, false) +DEF_OP(vid, a==0 ? 0 : b/a, side==at_left && x==1, false) +DEF_OP(vid2, a==0 ? 0 : div2(b,a), side==at_left && x==1, false) +DEF_OPF(mod, b==0 ? 0 : mod(a,b), b==0 ? 0 : a-b*gf_floor(a/b), + false, side==at_left && x==0 || side==at_right && x==1) +DEF_OPF(dom, a==0 ? 0 : mod(b,a), a==0 ? 0 : b-a*gf_floor(b/a), + false, side==at_left && x==0 || side==at_right && x==1) +//DEF_OPF(rem, b==0 ? 0 : a%b, b==0 ? 0 : a-b*gf_trunc(a/b)) +//DEF_OPF(mer, a==0 ? 0 : b%a, a==0 ? 0 : b-a*gf_trunc(b/a)) +DEF_OP(rem, b==0?0:a%b, false, side==at_left&&x==0 || side==at_right&&x==1) +DEF_OP(mer, a==0?0:b%a, false, side==at_left&&x==0 || side==at_right&&x==1) +#endif +#ifdef PASS2 +DEF_OP(gcd, gcd(a,b), x==0, x==1) +DEF_OP(gcd2, gcd2(a,b), x==0, x==1) // should test those and pick one of the two +DEF_OP(lcm, a==0 || b==0 ? 0 : lcm(a,b), x==1, x==0) +DEF_OPF(or , a|b, (float32)((int32)a | (int32)b), x==0, x==nt_all_ones(&x)) +DEF_OPF(xor, a^b, (float32)((int32)a ^ (int32)b), x==0, false) +DEF_OPF(and, a&b, (float32)((int32)a & (int32)b), x==nt_all_ones(&x), x==0) +DEF_OPF(shl, a<<b, a*pow(2.0,+b), side==at_right && x==0, false) +DEF_OPF(shr, a>>b, a*pow(2.0,-b), side==at_right && x==0, false) +DEF_OP(sc_and, a ? b : a, side==at_left && x!=0, side==at_left && x==0) +DEF_OP(sc_or, a ? a : b, side==at_left && x==0, side==at_left && x!=0) +DEF_OP(min, min(a,b), x==nt_greatest(&x), x==nt_smallest(&x)) +DEF_OP(max, max(a,b), x==nt_smallest(&x), x==nt_greatest(&x)) +#endif +#ifdef PASS3 +DEF_OP(cmp, cmp(a,b), false, false) +DEF_OP(eq, a == b, false, false) +DEF_OP(ne, a != b, false, false) +DEF_OP(gt, a > b, false, side==at_left&&x==nt_smallest(&x)||side==at_right&&x==nt_greatest(&x)) +DEF_OP(le, a <= b, false, side==at_left&&x==nt_smallest(&x)||side==at_right&&x==nt_greatest(&x)) +DEF_OP(lt, a < b, false, side==at_left&&x==nt_greatest(&x)||side==at_right&&x==nt_smallest(&x)) +DEF_OP(ge, a >= b, false, side==at_left&&x==nt_greatest(&x)||side==at_right&&x==nt_smallest(&x)) +DEF_OP(sin, (T)(b * sin(a * (M_PI / 18000))), false, false) // "LN=9000+36000n RA=0 LA=..." +DEF_OP(cos, (T)(b * cos(a * (M_PI / 18000))), false, false) // "LN=36000n RA=0 LA=..." +DEF_OP(atan, (T)(atan2(a,b) * (18000 / M_PI)), false, false) // "LA=0" +DEF_OP(tanh, (T)(b * tanh(a * (M_PI / 18000))), false, x==0) +DEF_OP(gamma, b<=0 ? 0 : (T)(0+floor(pow(a/256.0,256.0/b)*256.0)), false, false) // "RN=256" +DEF_OP(pow, ipow(a,b), false, false) // "RN=1" +DEF_OP(log, (T)(a==0 ? 0 : b * log(gf_abs(a))), false, false) // "RA=0" +// 0.7.8 +//DEF_OPF(clipadd, clipadd(a,b), a+b, x==0, false) +//DEF_OPF(clipsub, clipsub(a,b), a-b, side==at_right && x==0, false) +DEF_OP(abssub, gf_abs(a-b), false, false) +DEF_OP(sqsub, (a-b)*(a-b), false, false) +DEF_OP(avg, (a+b)/2, false, false) +DEF_OP(hypot, (T)(0+floor(sqrt(a*a+b*b))), false, false) +DEF_OP(sqrt, (T)(0+floor(sqrt(a))), false, false) +DEF_OP(rand, a==0 ? 0 : random()%(int32)a, false, false) +//DEF_OP(erf,"erf*", 0) +#endif + +extern Numop op_table1[], op_table2[], op_table3[]; +extern const long op_table1_n, op_table2_n, op_table3_n; + +#ifdef PASS1 +Numop op_table1[] = { + DECL_OP(ignore, "ignore", OP_ASSOC), + DECL_OP(put, "put", OP_ASSOC), + DECL_OP(add, "+", OP_ASSOC|OP_COMM), // "LINV=sub" + DECL_OP(sub, "-", 0), + DECL_OP(bus, "inv+", 0), + DECL_OP(mul, "*", OP_ASSOC|OP_COMM), + DECL_OP_NOFLOAT(mulshr8, "*>>8", OP_ASSOC|OP_COMM), + DECL_OP(div, "/", 0), + DECL_OP_NOFLOAT(div2, "div", 0), + DECL_OP(vid, "inv*", 0), + DECL_OP_NOFLOAT(vid2, "swapdiv", 0), + DECL_OP_NOFLOAT(mod, "%", 0), + DECL_OP_NOFLOAT(dom, "swap%", 0), + DECL_OP_NOFLOAT(rem, "rem", 0), + DECL_OP_NOFLOAT(mer, "swaprem", 0), +}; +const long op_table1_n = COUNT(op_table1); +#endif +#ifdef PASS2 +Numop op_table2[] = { + DECL_OP_NOFLOAT(gcd, "gcd", OP_ASSOC|OP_COMM), + DECL_OP_NOFLOAT(gcd2, "gcd2", OP_ASSOC|OP_COMM), + DECL_OP_NOFLOAT(lcm, "lcm", OP_ASSOC|OP_COMM), + DECL_OP(or , "|", OP_ASSOC|OP_COMM), + DECL_OP(xor, "^", OP_ASSOC|OP_COMM), + DECL_OP(and, "&", OP_ASSOC|OP_COMM), + DECL_OP_NOFOLD(shl, "<<", 0), + DECL_OP_NOFOLD(shr, ">>", 0), + DECL_OP_NOFOLD(sc_and,"&&", 0), + DECL_OP_NOFOLD(sc_or, "||", 0), + DECL_OP(min, "min", OP_ASSOC|OP_COMM), + DECL_OP(max, "max", OP_ASSOC|OP_COMM), +}; +const long op_table2_n = COUNT(op_table2); +#endif +#ifdef PASS3 +Numop op_table3[] = { + DECL_OP_NOFOLD(eq, "==", OP_COMM), + DECL_OP_NOFOLD(ne, "!=", OP_COMM), + DECL_OP_NOFOLD(gt, ">", 0), + DECL_OP_NOFOLD(le, "<=", 0), + DECL_OP_NOFOLD(lt, "<", 0), + DECL_OP_NOFOLD(ge, ">=", 0), + DECL_OP_NOFOLD(cmp, "cmp", 0), + DECL_OP_NOFOLD(sin, "sin*", 0), + DECL_OP_NOFOLD(cos, "cos*", 0), + DECL_OP_NOFOLD(atan, "atan", 0), + DECL_OP_NOFOLD(tanh, "tanh*", 0), + DECL_OP_NOFOLD(gamma, "gamma", 0), + DECL_OP_NOFOLD(pow, "**", 0), + DECL_OP_NOFOLD(log, "log*", 0), +// 0.7.8 +// DECL_OP(clipadd,"clip+", OP_ASSOC|OP_COMM), +// DECL_OP(clipsub,"clip-", 0), + DECL_OP_NOFOLD(abssub,"abs-", OP_COMM), + DECL_OP_NOFOLD(sqsub,"sq-", OP_COMM), + DECL_OP_NOFOLD(avg,"avg", OP_COMM), + DECL_OP_NOFOLD(hypot,"hypot", OP_COMM), + DECL_OP_NOFOLD(sqrt,"sqrt", 0), + DECL_OP_NOFOLD(rand,"rand", 0), + //DECL_OP_NOFOLD(erf,"erf*", 0), +}; +const long op_table3_n = COUNT(op_table3); +#endif + +// D=dictionary, A=table, A##_n=table count. +#define INIT_TABLE(D,A) { D=IEVAL(mGridFlow,"@"#D" ||= {}"); \ + for(int i=0; i<A##_n; i++) { \ + A[i].sym = ID2SYM(rb_intern(A[i].name)); \ + rb_hash_aset(D,A[i].sym,PTR2FIX((A+i)));}} + +#ifdef PASS1 +Ruby op_dict = Qnil; +Ruby number_type_dict = Qnil; +void startup_number () { + INIT_TABLE(op_dict,op_table1) + INIT_TABLE(op_dict,op_table2) + INIT_TABLE(op_dict,op_table3) + INIT_TABLE(number_type_dict,number_type_table) + + for (int i=0; i<COUNT(number_type_table); i++) { + number_type_table[i].index = (NumberTypeE) i; + char a[64]; + strcpy(a,number_type_table[i].aliases); + char *b = strchr(a,','); + if (b) { + *b=0; + rb_hash_aset(number_type_dict, ID2SYM(rb_intern(b+1)), + PTR2FIX(&number_type_table[i])); + } + rb_hash_aset(number_type_dict, ID2SYM(rb_intern(a)), + PTR2FIX(&number_type_table[i])); + } +// S:name; M:mode; F:replacement function; +#define OVERRIDE_INT(S,M,F) { \ + Numop *foo = FIX2PTR(Numop,rb_hash_aref(op_dict,SYM(S))); \ + foo->on_uint8.op_##M=F; \ + foo->on_int16.op_##M=F; \ + foo->on_int32.op_##M=F; } + OVERRIDE_INT(ignore,map,quick_ign_map); + OVERRIDE_INT(ignore,zip,quick_ign_zip); + //OVERRIDE_INT(put,map,quick_put_map); + //OVERRIDE_INT(put,zip,quick_put_zip); + //OVERRIDE_INT(%,map,quick_mod_map); // !@#$ does that make an improvement at all? +} +#endif diff --git a/externals/gridflow/base/source_filter.rb b/externals/gridflow/base/source_filter.rb new file mode 100644 index 00000000..59e24867 --- /dev/null +++ b/externals/gridflow/base/source_filter.rb @@ -0,0 +1,274 @@ +$keywords = %w(class decl def end grdecl) +$stack = [] +$classes = [] + +ClassDecl = Struct.new(:name,:supername,:methods,:grins,:attrs,:info) +MethodDecl = Struct.new(:rettype,:selector,:arglist,:minargs,:maxargs,:where) +Arg = Struct.new(:type,:name,:default) + +class MethodDecl + def ==(o) + return false unless rettype==o.rettype && + maxargs==o.maxargs # && minargs==o.minargs + arglist.each_index{|i| arglist[i] == o.arglist[i] or return false } + return true + end +end + +class Arg + def ==(o) + type==o.type && name==o.name # && default==o.default + end +end + +In = File.open ARGV[0], "r" +Out = File.open ARGV[1], "w" + +def handle_class(line) + raise "already in class #{where}" if $stack[-1] and ClassDecl===$stack[-1] + #STDERR.puts "class: #{line}" + /^(\w+)(?:\s*<\s*(\w+))?$/.match line or raise "syntax error #{where}" + q=ClassDecl.new($1,$2,{},{},{},false) + $stack << q + $classes << q + Out.puts "" +end + +def parse_methoddecl(line,term) + /^(\w+)\s+(\w+)\s*\(([^\)]*)\)\s*#{term}/.match line or + raise "syntax error #{where} #{line}" + rettype,selector,arglist = $1,$2,$3 + arglist,minargs,maxargs = parse_arglist arglist + MethodDecl.new(rettype,selector,arglist,minargs,maxargs,where) +end + +def parse_arglist(arglist) + arglist = arglist.split(/,/) + maxargs = arglist.length + args = arglist.map {|arg| + if /^\s*\.\.\.\s*$/.match arg then maxargs=-1; next end + /^\s*([\w\s\*<>]+)\s*\b(\w+)\s*(?:\=(.*))?/.match arg or + raise "syntax error in \"#{arg}\" #{where}" + type,name,default=$1,$2,$3 + Arg.new(type.sub(/\s+$/,""),name,default) + }.compact + minargs = args.length + minargs-=1 while minargs>0 and args[minargs-1].default + [args,minargs,maxargs] +end + +def unparse_arglist(arglist,with_default=true) + arglist.map {|arg| + x="#{arg.type} #{arg.name} " + x<<'='<<arg.default if with_default and arg.default + x + }.join(", ") +end + +def where + "[#{ARGV[0]}:#{$linenumber}]" +end + +def handle_attr(line) + type = line.gsub(%r"//.*$","").gsub(%r"/\*.*\*/","").gsub(%r";?\s*$","") + name = type.slice!(/\w+$/) + raise "missing \\class #{where}" if + not $stack[-1] or not ClassDecl===$stack[-1] + $stack[-1].attrs[name]=Arg.new(type,name,nil) + Out.print line + Out.puts "//FCS" + handle_decl "void _0_#{name}_m (#{type} #{name});" + Out.puts "# #{$linenumber}" +end + +def handle_decl(line) + raise "missing \\class #{where}" if + not $stack[-1] or not ClassDecl===$stack[-1] + classname = $stack[-1].name + m = parse_methoddecl(line,";\s*$") + $stack[-1].methods[m.selector] = m + + Out.print "#{m.rettype} #{m.selector}(int argc, Ruby *argv" + Out.print "," if m.arglist.length>0 + Out.print "#{unparse_arglist m.arglist});" + Out.puts "static Ruby #{m.selector}_wrap"+ + "(int argc, Ruby *argv, Ruby rself);//FCS" + Out.puts "# #{$linenumber+1}" +end + +def handle_def(line) + m = parse_methoddecl(line,"\{?.*$") + term = line[/\{.*/] + qlass = $stack[-1] + raise "missing \\class #{where}" if not qlass or not ClassDecl===qlass + classname = qlass.name + if qlass.methods[m.selector] + n = m; m = qlass.methods[m.selector] + if m!=n then + STDERR.puts "warning: def does not match decl:" + STDERR.puts "#{m.where}: \\decl #{m.inspect}" + STDERR.puts "#{n.where}: \\def #{n.inspect}" + end + else + qlass.methods[m.selector] = m + end + + Out.print "Ruby #{classname}::#{m.selector}_wrap"+ + "(int argc, Ruby *argv, Ruby rself) {"+ + "static const char *methodspec = "+ + "\"#{qlass.name}::#{m.selector}(#{unparse_arglist m.arglist,false})\";"+ + "DGS(#{classname});" + + Out.print "if (argc<#{m.minargs}" + Out.print "||argc>#{m.maxargs}" if m.maxargs!=-1 + Out.print ") RAISE(\"got %d args instead of %d..%d in %s\""+ + ",argc,#{m.minargs},#{m.maxargs},methodspec);" + + error = proc {|x,y| + "RAISE(\"got %s instead of #{x} in %s\","+ + "rb_str_ptr(rb_inspect(rb_obj_class(#{y}))),methodspec)" + } + + m.arglist.each_with_index{|arg,i| + case arg.type + when "Symbol" + Out.print "if (argc>#{i} && TYPE(argv[#{i}])!=T_SYMBOL) "+ + error[arg.type,"argv[#{i}]"]+";" + when "Array" + Out.print "if (argc>#{i} && TYPE(argv[#{i}])!=T_ARRAY) "+ + error[arg.type,"argv[#{i}]"]+";" + when "String" + Out.print "if (argc>#{i} && TYPE(argv[#{i}])==T_SYMBOL) "+ + "argv[#{i}]=rb_funcall(argv[#{i}],SI(to_s),0);" + Out.print "if (argc>#{i} && TYPE(argv[#{i}])!=T_STRING) "+ + error[arg.type,"argv[#{i}]"]+";" + end + } + +# Out.print "return " if m.rettype!="void" + Out.print "VALUE foo = " if m.rettype!="void" ### + + Out.print " self->#{m.selector}(argc,argv" + m.arglist.each_with_index{|arg,i| + if arg.default then + Out.print ",argc<#{i+1}?#{arg.default}:convert(argv[#{i}],(#{arg.type}*)0)" + else + Out.print ",convert(argv[#{i}],(#{arg.type}*)0)" + end + } + Out.print ");" + Out.print "self->check_magic();" + Out.print "return Qnil;" if m.rettype=="void" + Out.print "return foo;" if m.rettype!="void" ### + Out.print "} #{m.rettype} #{classname}::#{m.selector}(int argc, Ruby *argv" + Out.print "," if m.arglist.length>0 + Out.puts "#{unparse_arglist m.arglist, false})#{term}//FCS" +end + +def handle_classinfo(line) + frame = $stack[-1] + cl = frame.name + line="{}" if /^\s*$/ =~ line + Out.puts "static void #{cl}_startup (Ruby rself);" + Out.puts "static void *#{cl}_allocator () {return new #{cl};}" + Out.puts "static MethodDecl #{cl}_methods[] = {" + Out.puts frame.methods.map {|foo,method| + c,s = frame.name,method.selector + "{ \"#{s}\",(RMethod)#{c}::#{s}_wrap }" + }.join(",") + Out.puts "}; FClass ci#{cl} = { #{cl}_allocator, #{cl}_startup," + Out.puts "#{cl.inspect}, COUNT(#{cl}_methods), #{cl}_methods };" + Out.puts "void #{frame.name}_startup (Ruby rself) "+line +end + +def handle_grin(line) + fields = line.split(/\s+/) + i = fields[0].to_i + c = $stack[-1].name + Out.print "template <class T> void grin_#{i}(GridInlet *in, int n, Pt<T> data);" + Out.print "template <class T> static void grinw_#{i} (GridInlet *in, int n, Pt<T> data);" + Out.print "static GridHandler grid_#{i}_hand;" + handle_decl "Ruby _#{i}_grid(...);" + $stack[-1].grins[i] = fields.dup +end + +def handle_end(line) + frame = $stack.pop + fields = line.split(/\s+/) + n = fields.length + if ClassDecl===frame then + #handle_classinfo if not frame.info + cl = frame.name + if fields[0]!="class" or + (n>1 and fields[1]!=cl) + then raise "end not matching #{where}" end + $stack.push frame + frame.attrs.each {|name,attr| + type,name,default = attr.to_a + #STDERR.puts "type=#{type} name=#{name} default=#{default}" + handle_def "void _0_#{name}_m (#{type} #{name}) { this->#{name}=#{name}; }" + } + frame.grins.each {|i,v| + k = case v[1] + when nil; '4' + when 'int32'; '1' + when 'int'; '2' + when 'float'; 'F' + else raise 'BORK BORK BORK' end + Out.print "static GridHandler #{cl}_grid_#{i}_hand = GRIN#{k}(#{cl},#{i});" + handle_def "Ruby _#{i}_grid(...) {"+ + "if (in.size()<=#{i}) in.resize(#{i}+1);"+ + "if (!in[#{i}]) in[#{i}]=new GridInlet((GridObject *)this,&#{cl}_grid_#{i}_hand);"+ + "return in[#{i}]->begin(argc,argv);}" + + } + $stack.pop + Out.puts "# #{$linenumber}" + end + if :ruby==frame then + if fields[0]!="ruby" then raise "expected \\end ruby" end + end + Out.puts "" +end + +def handle_startall(line) + $classes.each {|q| + Out.print "rb_funcall(EVAL(\"GridFlow\"),SI(fclass_install),2,PTR2FIX(&ci#{q.name})," + if q.supername then + Out.print "EVAL(\"GridFlow::#{q.supername}\"));" + else + Out.print "Qnil);" + end + } + Out.puts "" +end + +def handle_ruby(line) + Out.puts "" + $stack.push :ruby +end + +$rubymode=false +$linenumber=1 +loop{ + x = In.gets + break if not x + if /^\s*\\(\w+)\s*(.*)$/.match x then + begin + send("handle_#{$1}",$2) + rescue StandardError => e + STDERR.puts e.inspect + STDERR.puts "at line #{$linenumber}" + STDERR.puts e.backtrace + File.unlink ARGV[1] + exit 1 + end + else + if $stack[-1]==:ruby then + x.gsub!(/([\\\"])/) { "\\"+$1 } + x="\"#{x.chomp}\\n\"\n" + end + Out.puts x + end + $linenumber+=1 +} diff --git a/externals/gridflow/base/test.rb b/externals/gridflow/base/test.rb new file mode 100644 index 00000000..1d44f918 --- /dev/null +++ b/externals/gridflow/base/test.rb @@ -0,0 +1,1086 @@ +# $Id: test.rb,v 1.1 2005-10-04 02:02:13 matju Exp $ + +$:.delete_if {|x| x=='.' } +require "gridflow" + +include GridFlow +GridFlow.verbose=true + +$imdir = "./images" +$animdir = "/opt/mex" +srand Time.new.to_i +$port = 4200+rand(100) + +def pressakey; puts "press return to continue."; readline; end + +class Expect < FObject + def praise(*a) + #raise(*a) + puts a + end + def expect(*v) + @count=0 + @v=v + @expecting=true + yield + @expecting=false + praise "wrong number of messages (#{@count}), expecting #{@v.inspect}" if @count!=@v.length + end + def _0_list(*l) + return if not @expecting + praise "wrong number of messages (#{@count})" if @count==@v.length + praise "got #{l.inspect} expecting #{@v.inspect}" if @v[@count]!=l + @count+=1 + end + def method_missing(s,*a) + praise "stray message: #{s}: #{a.inspect}" + end + install "expect", 1, 0 +end + +def test_bitpacking + #!@#$ WRITE ME +end + +def test_operators + #!@#$ WRITE ME +end + +def cast value, type + case type + when :uint8; value & 0xff + when :int16; (value & 0x7fff) - (value & 0x8000) + when :int32; value + when :int64; value + when :float32; value.to_f + when :float64; value.to_f + else raise "hell" + end +end + +def test_math + hm = "#".intern +#for nt in [:uint8, :int16, :int32, :int64, :float32, :float64] do +for nt in [:uint8, :int16, :int32, :float32] do + + #GridFlow.verbose = false + GridFlow.gfpost "starting test for #{nt}" + e = FObject["@export_list"] + x = Expect.new + e.connect 0,x,0 + + x.expect([1,2,3,11,12,13,21,22,23]) { + e.send_in 0, 3,3,nt,hm,1,2,3,11,12,13,21,22,23 } + + a = FObject["fork"] + b = FObject["@ +"] + a.connect 0,b,0 + a.connect 1,b,1 + b.connect 0,e,0 + x.expect([4]) { a.send_in 0, 2 } + + x.expect([2,3,5,7]) { e.send_in 0,"list #{nt} 2 3 5 7" } + a = FObject["@fold + {#{nt} # 0}"] + a.connect 0,e,0 + x.expect([cast(420000,nt)]) { a.send_in 0,"10000 #{nt} # 42" } + + (a = FObject["@ + {#{nt} 0 10}"]).connect 0,e,0 + x.expect([1,12,4,18,16,42,64]) { + a.send_in 0,:list,nt, 1,2,4,8,16,32,64 } + + a = FObject["@ + {#{nt} 2 3 5}"] + b = FObject["@fold + {#{nt} # 0}"] + a.connect 0,b,0 + b.connect 0,e,0 + x.expect([cast(45332,nt)]) { a.send_in 0, 1000,nt,hm,42 } + + + (a = FObject["@ + {#{nt} # 42}"]).connect 0,e,0 + x.expect((43..169).to_a) { + a.send_in 0,:list,nt, *(1..127).to_a } + + x.expect([3,5,9,15]) { + a.send_in 1,:list,4,nt,hm, 2,3,5,7 + a.send_in 0,:list,4,nt,hm, 1,2,4,8 } + x.expect([11,12,14,18]) { + a.send_in 1, "list #{nt} # 10" + a.send_in 0,:list,nt, 1,2,4,8 } + +if nt!=:uint8 and nt!=:float32 and nt!=:float64 + (a = FObject["@ / {#{nt} # 3}"]).connect 0,e,0 + x.expect([-2,-1,-1,-1,0,0,0,0,0,1,1,1,2]) { + a.send_in(0,:list,nt, *(-6..6).to_a) } + + (a = FObject["@ div {#{nt} # 3}"]).connect 0,e,0 + x.expect([-2,-2,-2,-1,-1,-1,0,0,0,1,1,1,2]) { + a.send_in(0, :list, nt, *(-6..6).to_a) } +end + + (a = FObject["@ ignore {#{nt} # 42}"]).connect 0,e,0 + x.expect((42..52).to_a) { a.send_in(0, :list, nt, *(42..52).to_a) } + + (a = FObject["@ put {#{nt} # 42}"]).connect 0,e,0 + x.expect([42]*13) { a.send_in(0, :list, nt, *(-6..6).to_a) } + +if nt!=:uint8 + (a = FObject["@! abs"]).connect 0,e,0 + x.expect([2,3,5,7]) { + a.send_in 0,:list,nt, -2,3,-5,7 } +end + + (a = FObject["@fold * {#{nt} # 1}"]).connect 0,e,0 + x.expect([210]) { a.send_in 0,:list,nt, 2,3,5,7 } + x.expect([128]) { a.send_in 0,:list,nt, 1,1,2,1,2,2,2,1,1,2,1,2,2 } + + (a = FObject["@fold + {#{nt} 0 0}"]).connect 0,e,0 + x.expect([18,23]) { a.send_in 0, 3,2,nt,hm,2,3,5,7,11,13 } + + (a = FObject["@scan + {#{nt} 0 0}"]).connect 0,e,0 + x.expect([2,3,7,10,18,23]) { a.send_in 0, 3,2,nt,hm,2,3,5,7,11,13 } + + (a = FObject["@scan * {#{nt} # 1}"]).connect 0,e,0 + x.expect([2,6,30,210]) { a.send_in 0,:list,nt, 2,3,5,7 } + x.expect([1,1,2,2,4,8,16,16,16,32,32,64,128]) { + a.send_in 0,:list,nt, 1,1,2,1,2,2,2,1,1,2,1,2,2 } + + (a = FObject["@scan + {#{nt} 0 0 0}"]).connect 0,e,0 + x.expect([1,2,3,5,7,9,12,15,18]) { + a.send_in 0,:list,3,3,nt,hm,*(1..9).to_a } + + (a = FObject["@scan + {#{nt} # 0}"]).connect 0,e,0 + x.expect([1,3,6, 4,9,15, 7,15,24]) { + a.send_in 0,:list,3,3,nt,hm,*(1..9).to_a } + + (a = FObject["@outer +"]).connect 0,e,0 + x.expect([9,10,12,17,18,20,33,34,36]) { + a.send_in 1,:list,nt, 1,2,4 + a.send_in 0,:list,nt, 8,16,32 } + + x.expect((0...100).to_a) { + a.send_in 1,(0...10).to_a + a.send_in 0,(0...10).map{|i| 10*i }} + +if nt!=:uint8 and nt!=:float32 and nt!=:float64 + (a = FObject["@outer",:%,[nt,3,-3]]).connect 0,e,0 + x.expect([0,0,1,-2,2,-1,0,0,1,-2,2,-1,0,0]) { + a.send_in 0,:list,nt, -30,-20,-10,0,+10,+20,+30 } + + (a = FObject["@outer","swap%".intern,[nt,3,-3]]).connect 0,e,0 + x.expect([-27,-3,-17,-3,-7,-3,0,0,3,7,3,17,3,27]) { + a.send_in 0,:list,nt, -30,-20,-10,0,+10,+20,+30 } +end + + (a = FObject["@import {3}"]).connect 0,e,0 + x.expect([2,3,5]) { [2,3,5].each {|v| a.send_in 0,:list,nt,hm,v }} + + (a = FObject["@import {3}"]).connect 0,e,0 + x.expect([2,3,5]) { [2,3,5].each {|v| a.send_in 0,:list,nt,v }} + + (a = FObject["@redim {5}"]).connect 0,e,0 + x.expect([2,3,5,2,3]) { a.send_in 0,:list,2,3,5 } + + (a = FObject["@redim {5}"]).connect 0,e,0 + x.expect([0,0,0,0,0]) { a.send_in 0,:list } + + (a = FObject["@redim {0}"]).connect 0,e,0 + x.expect([]) { a.send_in 0,:list,42,37,69 } + + (a = FObject["@inner * + {#{nt} # 0} {2 2 #{nt} # 2 3 5 7}"]).connect 0,e,0 + (i0 = FObject["@redim {2 2}"]).connect 0,a,0 + x.expect([12,17,48,68]) { i0.send_in 0,:list,nt, 1,2,4,8 } + +#if nt!=:int64 +if false + a = FObject["@print"] + a.send_in 0, "3 3 #{nt} # 1 0 0 0" + a.send_in 0, "3 3 3 #{nt} # 1 2 3 4" + a.send_in 0, "base 16" + a.send_in 0, "3 3 3 #{nt} # 255 0 0 0" +end + + (a = FObject["@outer * {3 2 #{nt} # 1 2 3}"]).connect 0,e,0 + b = FObject["@dim"] + c = FObject["@export_list"] + a.connect 0,b,0 + y = Expect.new + b.connect 0,c,0 + c.connect 0,y,0 + + y.expect([2,3,2]) { + x.expect([1,2,3,1,2,3,10,20,30,10,20,30]) { + a.send_in 0,:list,nt, 1, 10 }} + + #pr=GridPrint.new + (b = FObject["@redim {5 5}"]).connect 0,e,0 + (a = FObject["@convolve * + {#{nt} # 0}"]).connect 0,b,0 + (i0 = FObject["@redim {5 5 1}"]).connect 0,a,0 + (i1 = FObject["@redim {3 1}"]).connect 0,a,1 + i1.send_in 1, 3,3 + x.expect([5,6,5,4,4,4,6,7,6,4,3,3,6,7,5,4,2,3,6,6,5,4,3,4,5]) { + a.send_in 1,:list,3,3,nt,hm, 1,1,1,1,1,1,1,1,1 + i0.send_in 0,:list,nt, 1,1,1,0,0,0 } + + (a = FObject["@convolve * + {#{nt} # 0}"]).connect 0,e,0 + x.expect([1,3,6,4,0]) { + a.send_in 1, 1,2,nt,hm, 1,1 + a.send_in 0, 1,5,nt,hm, 0,1,2,4,0 } + + (a = FObject["@import {4}"]).connect 0,e,0 + x.expect([2,3,5,7]) { + [2,3,5,7].each {|v| a.send_in 0,v }} + x.expect([1,2,3],[4,5,6],[7,8,9]) { + a.send_in 1, :list, 3 + a.send_in 0, :list, *(1..9).to_a} + + for o in ["@store"] + (a = FObject[o]).connect 0,e,0 + a.send_in 1, 5, 4, nt, hm, 1,2,3,4,5 + x.expect([1,2,3,4,4,5,1,2,2,3,4,5]) { + a.send_in 0, 3,1, hm, 0,2,4 } + x.expect([1,2,3,4,5]*24) { a.send_in 0, 2,3,0,hm } + x.expect([1,2,3,4,5]*4) { a.send_in 0, 0,hm } + x.expect([1,2,3,4,5]*4) { a.send_in 0 } + x.expect([1,2,3,4]) { a.send_in 0,[0] } + a.send_in 1,:put_at,[0,0] + a.send_in 1,2,2,nt,hm,6,7,8,9 + x.expect([6,7,3,4, 8,9,2,3, 4,5,1,2, 3,4,5,1, 2,3,4,5]) { a.send_in 0 } + x.expect([6,7,3,4]) { a.send_in 0,[0] } + x.expect([8,9,2,3]) { a.send_in 0,[1] } + a.send_in 1,:put_at,[1,1] + a.send_in 1,2,2,nt,hm,11,13,17,19 + x.expect([6,7,3,4, 8,11,13,3, 4,17,19,2, 3,4,5,1, 2,3,4,5]) { a.send_in 0 } + end + + b = FObject["@dim"] + c = FObject["@export_list"] + a.connect 0,b,0 + y = Expect.new + b.connect 0,c,0 + c.connect 0,y,0 + +if nt!=:uint8 and nt!=:float32 and nt!=:float64 and nt!=:int64 + (a = FObject["@for {#{nt} # 0} {#{nt} # 10} {#{nt} # 1}"]).connect 0,e,0 + a.connect 0,b,0 + y.expect([10]) { + x.expect((0...10).to_a) { + a.send_in 0 } } + + (a = FObject["@for {#{nt} # 0} {#{nt} # -10} {#{nt} # 1}"]).connect 0,e,0 + a.connect 0,b,0 + y.expect([0]) { x.expect([]) { a.send_in 0 } } + + (a = FObject["@for {#{nt} # 0} {#{nt} # -10} {#{nt} # -1}"]).connect 0,e,0 + a.connect 0,b,0 + y.expect([10]) { x.expect([0,-1,-2,-3,-4,-5,-6,-7,-8,-9]) { a.send_in 0 } } + + (a = FObject["@for {#{nt} 0} {#{nt} 10} {#{nt} 1}"]).connect 0,e,0 + a.connect 0,b,0 + y.expect([10,1]) { + x.expect((0...10).to_a) { + a.send_in 0 } } + + (a = FObject["@for {#{nt} 2 3} {#{nt} 5 7} {#{nt} 1 1}"]).connect 0,e,0 + a.connect 0,b,0 + y.expect([3,4,2]) { + x.expect([2,3,2,4,2,5,2,6,3,3,3,4,3,5,3,6,4,3,4,4,4,5,4,6]) { + a.send_in 0 } } +end + + (a = FObject["@complex_sq"]).connect 0,e,0 + x.expect([8,0]) { a.send_in 0, 2, 2 } + x.expect([0,9]) { a.send_in 0, 0, 3 } + + (a = FObject["@rotate 3000 {1 2 5}"]).connect 0,e,0 +# pr = GridPrint.new +# a.connect 0,pr,0 +# x.expect([]) { + a.send_in 0, "5 5 # 1000 0 0 0 0 0" +# } + +#if nt==:float32 or nt==:float64 +# (a = FObject["@matrix_solve"]).connect 0,e,0 +# x.expect([1,0,0,0,1,0,0,0,1]) { a.send_in 0, 3, 3, nt, hm, 1,0,0,0,1,0,0,0,1 } +#end + GridFlow.gfpost "ending test for #{nt}" +end # for nt + + (a = FObject["@two"]).connect 0,e,0 + x.expect([42,0]) { a.send_in 0,42 } + x.expect([42,28]) { a.send_in 1,28 } + x.expect([1313,28]) { a.send_in 0,1313 } + + (a = FObject["@three"]).connect 0,e,0 + x.expect([42,0,0]) { a.send_in 0,42 } + x.expect([42,28,0]) { a.send_in 1,28 } + x.expect([42,28,-1]) { a.send_in 2,-1 } + + (a = FObject["@four"]).connect 0,e,0 + x.expect([42,0,0,0]) { a.send_in 0,42 } + x.expect([42,0,0,-42]) { a.send_in 3,-42 } + +# glob = FObject["@global"] +# glob.send_in 0, "profiler_dump" + + e = FObject["@export_list"] + e.connect 0,x,0 + + a = FObject["@import per_message"] + a.connect 0,e,0 + x.expect([1,2,3]) { a.send_in 0,1,2,3 } + x.expect([102,111,111]) { a.send_in 0,:symbol,:foo } + x.expect([ 70, 79, 79]) { a.send_in 0,:symbol,:FOO } + + a = FObject["@join 1"] + a.connect 0,e,0 + a.send_in 1,2,2,nt,hm,11,13,17,19 + x.expect([2,3,11,13,5,7,17,19]) { a.send_in 0,2,2,nt,hm,2,3,5,7 } + +if nt!=:float64; #!@#$ + a.send_in 1, 5,1,nt,hm,42 + y.expect([5,4]) { + x.expect([2,3,5,42,7,11,13,42,17,19,23,42,29,31,37,42,41,43,47,42]) { + a.send_in 0, 5,3,nt,hm,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47 }} +end + a = FObject["@join 0"] + a.connect 0,e,0 + a.send_in 1,2,2,nt,hm,11,13,17,19 + x.expect([2,3,5,7,11,13,17,19]) { a.send_in 0,2,2,nt,hm,2,3,5,7 } + + a = FObject["@join 0 {2 2 2 #{nt} # 1 2 3}"] + a.connect 0,e,0 + a.connect 0,b,0 + y.expect([2,2,2]) { x.expect([1,2,3,1,2,3,1,2]) { a.send_in 0,0,2,2,nt,hm }} + + a = FObject["@ravel"] + b = FObject["@dim"] + be = FObject["@export_list"] + bx = Expect.new + a.connect 0,e,0 + a.connect 0,b,0 + b.connect 0,be,0 + be.connect 0,bx,0 + bx.expect([9]) { + x.expect([1,2,3,2,4,6,3,6,9]) { + o = FObject["@outer *"] + o.connect 0,a,0 + o.send_in 1,1,2,3 + o.send_in 0,1,2,3 + } + } + + a = FObject["@grade"] + a.connect 0,e,0 + x.expect([0,2,4,6,8,9,7,5,3,1]) { a.send_in 0, 0,9,1,8,2,7,3,6,4,5 } + x.expect([0,9,1,8,2,7,3,6,4,5]) { a.send_in 0, 0,2,4,6,8,9,7,5,3,1 } + x.expect([7,6,5,4,3,2,1,0]) { a.send_in 0, 7,6,5,4,3,2,1,0 } + + a = FObject["@grade"] + b = FObject["@fold +"] + a.connect 0,b,0 + b.connect 0,e,0 + x.expect([100*99/2]) { a.send_in 0, (0...100).map { (rand*0x10000).to_i }} + x.expect([100*99/2]) { a.send_in 0, (0...100).map { (rand*0x10).to_i }} + x.expect([100*99/2]) { a.send_in 0, (0...100).map { 0 }} + + a = FObject["@perspective"] + a.connect 0,e,0 + c = [] + 8.times {|v| + 3.times {|i| + c << (v[i] * 1000 - 500) + (if i==2 then 2000 else 0 end) + } + } + x.expect([ + -85,-85,85,-85,-85,85,85,85, + -51,-51,51,-51,-51,51,51,51]) { + a.send_in 0, 8,3,hm,*c } + +# regressiontests for past bugs + a = FObject["@inner"] # that's it. +end + +def test_rtmetro + rt = FObject["rtmetro 1000"] + pr = FObject["rubyprint"] + rt.connect 0,pr,0 + GridFlow.post "trying to start the rtmetro" + rt.send_in 0,1 + $mainloop.timers.after(10.0) { + GridFlow.post "trying to stop the rtmetro (after 10 sec delay)" + rt.send_in 0,0 + } + $mainloop.loop +end + +def test_print + i = FObject["@redim {3}"] + pr = FObject["@print"] +# pr = GridFlow::RubyPrint.new + i.connect 0,pr,0 + i.send_in 0, 85, 170, 255 + i.send_in 1, 3, 3 + i.send_in 0, 1, 0, 0, 0 + i.send_in 1, 2, 2, 2 + i.send_in 0, 2, 3, 5, 7, 11, 13, 17, 19 +end + +class Barf < GridObject + def _0_rgrid_begin + raise "barf" + end + install_rgrid 0 + install "barf", 1, 0 +end + +def test_nonsense +# (a = FObject["@! abs"]).connect 0,e,0 +# x.expect_error { +# a.send_in 1, 42,42 } + + a = FObject["@import {3}"] + b = Barf.new + a.connect 0,b,0 + begin + a.send_in 0, 1, 2, 3 + rescue StandardError + nil + else + raise "Expected StandardError" + end + p b.inlet_dim(0) +end + +def test_store + a = FObject["@in file #{$imdir}/teapot.png"] + b = FObject["@store"] + c = FObject["@out x11"] + a.connect 0,b,1 + a.send_in 0,"cast uint8" + a.send_in 0 + b.connect 0,c,0 + d = FObject["@for {0 0} {256 256} {1 1}"] + e = FObject["@ ^ 85"] + d.connect 0,e,0 + e.connect 0,b,0 + f = FObject["fps detailed"] + c.connect 0,f,0 + pr = FObject["rubyprint"] + f.connect 0,pr,0 + GridFlow.verbose = false + 256.times {|t| + e.send_in 1,t + d.send_in 0 + } +end + +# generates recursive checkerboard pattern (munchies) in bluish colours. +class Munchies < FPatcher + @fobjects = ["fork","fork","@for 0 64 1","@for 0 64 1","@for 2 5 1", + "@outer ^","@outer *"] + @wires = [-1,0,0,0, 0,0,1,0, 1,1,4,0, 4,0,6,1, + 1,0,3,0, 3,0,5,1, 0,0,2,0, 2,0,5,0, 5,0,6,0, 6,0,-1,0 ] + def initialize() super end + install "munchies",1,1 +end + +def test_munchies + m=Munchies.new + gout = FObject["@out quartz"] + m.connect 0,gout,0 + m.send_in 0 + $mainloop.loop +end + +def test_image command + gin = FObject["@in"] + gout = FObject["@out x11"] + gin.connect 0,gout,0 +# 31.times { + 3.times { + gin.send_in 0,"open #{command}" + gout.send_in 0,"timelog 1" + gin.send_in 0 + } + FObject["@global"].send_in 0, "profiler_dump" + $mainloop.loop +end + +def test_ppm2 + gin = FObject["@in"] + store = FObject["@store"] + pa = FObject["@convolve << + 0"] + pb = FObject["@ / 9"] + ra = FObject["@redim {3 3}"] + gout = FObject["@out x11"] + gin.connect 0,store,1 + store.connect 0,pa,0 + pa.connect 0,pb,0 + pb.connect 0,gout,0 + ra.connect 0,pa,1 + ra.send_in 0,"0 0" + gout.send_in 0,"timelog 1" + gin.send_in 0,"open file #{$imdir}/teapot.png" +# gin.send_in 0,"open file #{$imdir}/g001.jpg" + gin.send_in 0 +# 40.times { store.send_in 0 } + loop { store.send_in 0 } + v4j = FObject["@global"] + v4j.send_in 0,"profiler_dump" +# $mainloop.loop +end + +def test_foo + foo = FObject["@for {0 0} {64 64} {1 1}"] + che = FObject["@checkers"] + sca = FObject["@scale_by {5 3}"] + out = FObject["@out x11"] + foo.connect 0,che,0 + che.connect 0,sca,0 + sca.connect 0,out,0 + foo.send_in 0 + $mainloop.loop +end + +def test_anim(*msgs) + GridFlow.verbose = false + gin = FObject["@in"] + #gout1 = FObject["@out x11"] + gout1 = FObject["@out sdl"] + #gout1 = FObject["@out quicktime file test.mov"] + #gout1.send_in 0, :codec, :jpeg + fps = FObject["fps detailed"] + rpr = FObject["rubyprint"] + gout1.connect 0,fps,0 + #fps.connect 0,rpr,0 +=begin + gout1 = FObject["@downscale_by {3 2}"] + gout2 = FObject["@rgb_to_greyscale"] + gout3 = FObject["@out aalib X11 -height 60 -width 132"] + gout1.connect 0,gout2,0 + gout2.connect 0,gout3,0 +=end + + rpr = FObject["rubyprint"] +# gin.connect 1,rpr,0 + + gin.connect 0,gout1,0 +=begin + layer=FObject["@layer"] + gin.connect 0,layer,0 + layer.connect 0,gout1,0 + check=FObject["@checkers"] + phor=FObject["@for {0 0} {256 256} {1 1}"] + phor.connect 0,check,0 + check.connect 0,layer,1 + phor.send_in 0 +=end + +# scale = FObject["@scale_by 3"] +# gin.connect 0,scale,0 +# scale.connect 0,gout1,0 + +# pr = FObject["rubyprint time"]; gout.connect 0,pr,0 + msgs.each {|m| gin.send_in 0,m } + gin.send_in 0, "cast uint8" +# gout.send_in 0,"timelog 1" + d=Time.new + frames=2000 + frames.times {|n| + #GridFlow.post "%d", n + gin.send_in 0 + #gin.send_in 0, rand(1000) + } +# loop { gin.send_in 0 } +# metro = FObject["rtmetro 80"] +# metro.connect 0,gin,0 +# metro.send_in 0,1 +# $mainloop.loop + + d=Time.new-d + printf "%d frames in %.6f seconds (avg %.6f ms, %.6f fps)\n", + frames, d, 1000*d/frames, frames/d +# global.send_in 0,"dfgdfgdkfjgl" + gout1.send_in 0, :close + global = FObject["@global"] + global.send_in 0,"profiler_dump" +end + +class TestTCP < FObject + attr_accessor :idle + def initialize + @idle = true + end + def _0_bang + # GridFlow.gfpost "tick" + # avoid recursion + $mainloop.timers.after(0) { + ($in_client.send_in 0; @idle=false) if @idle + } + end + install "tcptest",1,1 +end + +def test_tcp + if fork + # client (is receiving) + GridFlow.post_header = "[client] " + $in_client = in1 = FObject["@in"] + out = FObject["@out x11"] + in1.connect 0,out,0 + out.send_in 0,"timelog 1" + out.send_in 0,"autodraw 2" + GridFlow.post "test: waiting 1 second" + sleep 1 + p "HI" + #in1.send_in 0,"open grid tcp localhost #{$port}" + in1.send_in 0,"open grid tcp 127.0.0.1 #{$port}" + p "HI" + + test_tcp = TestTCP.new + + out.connect 0,test_tcp,0 + test_tcp.connect 0,in1,0 + + GridFlow.post "entering mainloop..." + $mainloop.loop + else + # server (is sending) + GridFlow.post_header = "[server] " + $in1_server = in1 = FObject["@in"] + $in2_server = in2 = FObject["@in"] + $out = out = FObject["@out"] + toggle = 0 + in1.connect 0,out,0 + in2.connect 0,out,0 + in1.send_in 0,"open #{$imdir}/r001.jpg" + in2.send_in 0,"open #{$imdir}/b001.jpg" + out.send_in 0,"open grid tcpserver #{$port}" + out.send_in 0,"type uint8" + test_tcp = GridFlow::FObject.new + def test_tcp._0_bang + # GridFlow.post "tick" + @toggle ||= 0 + # avoid recursion + $mainloop.timers.after(0.01) { + if $out.format.stream + if @toggle==0; $in1_server else $in2_server end.send_in 0 + @toggle ^= 1 + end + _0_bang + } + end + out.connect 0,test_tcp,0 + test_tcp.send_in 0 + GridFlow.post "entering mainloop..." + $mainloop.loop + end +end + +def test_layer + + gin = FObject["@in png file ShaunaKennedy/atmosphere-bleu.png"] +# gin1 = FObject["@in file #{$imdir}/r001.jpg"] +# gin = FObject["@join 2 {240 320 1 # 128}"] +# gin1.connect 0,gin,0 + +# gfor = FObject["@for {0 0} {120 160} {1 1}"] +# gfor = FObject["@for {0 0} {240 320} {1 1}"] + gfor = FObject["@for {0 0} {480 640} {1 1}"] + gche = FObject["@checkers"] + + gove = FObject["@layer"] +# gove = FObject["@fold + {0 0 0 0}"] +# gout = FObject["@print"] +# gove = FObject["@inner2 * + 0 {3 4 # 1 0 0 0 0}"] +# gout = FObject["@out sdl"] + gout = FObject["@out x11"] + + gin.connect 0,gove,0 + gfor.connect 0,gche,0 + gche.connect 0,gove,1 + gove.connect 0,gout,0 + + gfor.send_in 0 + + fps = FObject["fps detailed"] + pr = FObject["rubyprint"] + gout.connect 0,fps,0 + fps.connect 0,pr,0 + + loop{gin.send_in 0} +# gin.send_in 0 +# gin1.send_in 0 + $mainloop.loop +end + +Images = [ + "png file opensource.png", +# "png file ShaunaKennedy/atmosphere.png", +# "targa file #{$imdir}/tux.tga", +# "png file #{$imdir}/lena.png", + "file #{$imdir}/ruby0216.jpg", + "file #{$imdir}/g001.jpg", + "file #{$imdir}/teapot.tga", + "grid gzfile #{$imdir}/foo.grid.gz", + "grid gzfile #{$imdir}/foo2.grid.gz", +# "videodev /dev/video0", +] + +def test_formats_speed + gin = FObject["@in"] + gout = FObject["@out x11"] + gin.connect 0,gout,0 + GridFlow.verbose=false + t1=[] + Images.each {|command| + gin.send_in 0,"open #{command}" + t0 = Time.new + 10.times {gin.send_in 0} + t1 << (Time.new - t0) + sleep 1 + } + p t1 +end + +def test_formats + gin = FObject["@in"] + gout = FObject["@out window"] + gs = FObject["@ + {int16 # 0}"] + gin.connect 0,gs,0 + gs.connect 0,gout,0 +# GridFlow.verbose=false + t1=[] + Images.each {|command| + gin.send_in 0,"open #{command}" + gin.send_in 0,"cast int16" + # test for load, rewind, load + 5.times {|x| gs.send_in 1, [:int16, '#'.intern, x*128]; gin.send_in 0} + # test for filehandle leak + #1000.times { gin.send_in 0,"open #{command}" } + sleep 1 + } + p t1 +end + +def test_rewind + gin = FObject["@in videodev /dev/video1 noinit"] + gin.send_in 0, "transfer read" + gout = FObject["@out ppm file /tmp/foo.ppm"] +# gout = FObject["@out x11"] + gin.connect 0,gout,0 + loop { + gin.send_in 0 + gout.send_in 0, "rewind" + } +end + +def test_formats_write + # read files, store and save them, reload, compare, expect identical + a = FObject["@in"] + b = FObject["@out"]; a.connect 0,b,0 + c = FObject["@ -"]; a.connect 0,c,1 + d = FObject["@in"]; d.connect 0,c,0 + e = FObject["@fold +"]; c.connect 0,e,0 + f = FObject["@fold +"]; e.connect 0,f,0 + g = FObject["@fold +"]; f.connect 0,g,0 + h = FObject["@ / 15000"]; g.connect 0,h,0 + i = FObject["@export_list"]; h.connect 0,i,0 + x = Expect.new; i.connect 0,x,0 + [ + ["ppm file", "#{$imdir}/g001.jpg"], + ["targa file", "#{$imdir}/teapot.tga"], + ["targa file", "#{$imdir}/tux.tga"], + ["jpeg file", "#{$imdir}/ruby0216.jpg"], + ["grid gzfile", "#{$imdir}/foo.grid.gz", "endian little"], + ["grid gzfile", "#{$imdir}/foo.grid.gz", "endian big"], + ].each {|type,file,*rest| + a.send_in 0, "open #{type} #{file}" + b.send_in 0, "open #{type} /tmp/patate" + rest.each {|r| b.send_in 0,r } + a.send_in 0 + b.send_in 0, "close" + raise "written file does not exist" if not File.exist? "/tmp/patate" + d.send_in 0, "open #{type} /tmp/patate" + x.expect([0]) { d.send_in 0 } +# d.send_in 0 + } +end + +def test_mpeg_write + a = FObject["@in ppm file /opt/mex/r.ppm.cat"] + b = FObject["@out x11"] + a.connect 0,b,0 + loop{a.send_in 0} +end + +def test_headerless + gout = FObject["@out"] + gout.send_in 0, "open grid file #{$imdir}/hello.txt" + gout.send_in 0, "headerless" + gout.send_in 0, "type uint8" + gout.send_in 0, "104 101 108 108 111 32 119 111 114 108 100 33 10" + gout.send_in 0, "close" + gin = FObject["@in"] + pr = FObject["@print"] + gin.connect 0,pr,0 + gin.send_in 0, "open grid file #{$imdir}/hello.txt" + gin.send_in 0, "headerless 13" + gin.send_in 0, "type uint8" + gin.send_in 0 +end + + +def test_sound +# o2 = FObject["@ * 359"] # @ 439.775 Hz +# o1 = FObject["@for 0 44100 1"] # 1 sec @ 1.225 Hz ? + o1 = FObject["@for 0 4500 1"] + o2 = FObject["@ * 1600"] # @ 439.775 Hz + o3 = FObject["@ sin* 255"] + o4 = FObject["@ gamma 400"] + o5 = FObject["@ << 7"] + out = FObject["@out"] + o1.connect 0,o2,0 + o2.connect 0,o3,0 + o3.connect 0,o4,0 + o4.connect 0,o5,0 + o5.connect 0,out,0 +# out.send_in 0,"open raw file /dev/dsp" + out.send_in 0,"open grid file /dev/dsp" + out.send_in 0,"type int16" + x=0 + loop { + o4.send_in 1, x + o1.send_in 0 + x+=10 + } +end + +include Math +def test_polygon + o1 = FObject["@for 0 5 1"] + o2 = FObject["@ * 14400"] + o3 = FObject["@outer + {0 9000}"] + o4 = FObject["@ +"] + o5 = FObject["@ cos* 112"] + o6 = FObject["@ + {120 160}"] + poly = FObject["@draw_polygon + {3 uint8 # 255}"] +if false + out1 = FObject["@cast int32"] + out2 = FObject["@solarize"] +# out1 = FObject["@downscale_by 2 smoothly"] + out3 = FObject["@out x11"] + out1.connect 0,out2,0 + out2.connect 0,out3,0 +else + out1 = FObject["@out x11"] + fps = FObject["fps detailed cpu"] + out1.connect 0,fps,0 + pr = FObject["rubyprint"] + fps.connect 0,pr,0 +end + store = FObject["@store"]; store.send_in 1, "240 320 3 uint8 # 0" +# store2 = FObject["@store"] + store.connect 0,poly,0 + poly.connect 0,store,1 +# store2.connect 0,store,1 + o1.connect 0,o2,0 + o2.connect 0,o3,0 + o3.connect 0,o4,0 + o4.connect 0,o5,0 + o5.connect 0,o6,0 + o6.connect 0,poly,2 +# cast = FObject["@cast int32"] +# poly.connect 0,cast,0 +# cast.connect 0,out1,0 + poly.connect 0,out1,0 + x=0 + GridFlow.verbose=false + task=proc { + o4.send_in 1, 5000*x + o5.send_in 1, 200+200*sin(x) + poly.send_in 1,:list,:uint8, *(0..2).map{|i| 4+4*cos(0.2*x+i*PI*2/3) } + o1.send_in 0 + store.send_in 0 +# store2.send_in 0 + x+=1 + if x<1000 then $mainloop.timers.after(0.0) {task[]} + else GridGlobal.new.send_in 0,"profiler_dump"; exit end + } + task[] + $mainloop.loop +end + +class FRoute2 < FObject + def initialize(selector) + @selector = selector.to_s + end + def method_missing(sym,*a) + sym=sym.to_s + if sym =~ /^_0_(.*)/ + send_out((if $1==@selector then 0 else 1 end), $1.intern, *a) + else super end + end + install "route2", 1, 2 +end + +def test_aalib +# gin = FObject["@in ppm file #{$imdir}/r001.jpg"] + gin = FObject["@in ppm file #{$animdir}/b.jpg.cat"] + grey = FObject["@rgb_to_greyscale"] + cont = FObject["@ << 1"] + clip = FObject["@ min 255"] + gout = FObject["@out aalib X11"] + sto = FObject["@store"] + op = FObject["aa_fill_with_text {localhost 4242}"] + filt = FObject["route2 grid"] + gin.connect 0,grey,0 + grey.connect 0,cont,0 + cont.connect 0,clip,0 + clip.connect 0,gout,0 + gout.connect 0,filt,0 + filt.connect 0,sto,1 + sto.connect 0,op,0 + op.connect 0,gout,0 + gout.send_in 0, :autodraw, 0 + GridFlow.verbose = false + task=proc{ + gin.send_in 0 + gout.send_in 0, :dump + sto.send_in 0 + gout.send_in 0, :draw + $mainloop.timers.after(0.1,&task) + } + task[] + $mainloop.loop +end + +def test_store2 + o = [ + FObject["@for {0 0} {240 320} {1 1}"], + FObject["@ / 2"], + FObject["@ + 0"], + FObject["@ + 0"], + FObject["@store"], + FObject["@out x11"]] + (0..4).each {|x| o[x].connect 0,o[x+1],0 } + q = FObject["@in ppm file images/r001.jpg"] + q.connect 0,o[4],1 + q.send_in 0 + o[0].send_in 0 + $mainloop.loop +end + +def test_remap + rem = FObject["@remap_image"] + rot = FObject["@rotate 4000"] + rem.connect 1,rot,0 + rot.connect 0,rem,1 + gin = FObject["@in ppm file #{$imdir}/teapot.png"] + gout = FObject["@out x11"] + gin.connect 0,rem,0 + rem.connect 0,gout,0 + gin.send_in 0 + $mainloop.loop +end + +def test_asm + GridFlow.verbose=false + a = FObject["@in ppm file images/r001.jpg"] + aa = FObject["@cast uint8"] + b = FObject["@store"] + d = FObject["@store"] + a.connect 0,aa,0 + aa.connect 0,b,1 + aa.connect 0,d,1 + a.send_in 0 + c = FObject["@ + {uint8 # 0}"] + t0 = Time.new; 1000.times {b.send_in 0}; t1 = Time.new-t0 + t1 *= 1 + b.connect 0,c,0 + stuff=proc{ + 3.times{ + t0 = Time.new; 1000.times {b.send_in 0}; t2 = Time.new-t0 + t2 *= 1 + GridFlow.post " %f %f %f", t1, t2, t2-t1 + } + } + puts "map:" + stuff[] + d.connect 0,c,1 # for zip + d.send_in 0 + puts "zip:" + stuff[] +end + +def test_metro + o1 = RtMetro.new(1000,:geiger) + o2 = RubyPrint.new(:time) + o1.connect 0,o2,0 + o1.send_in 0,1 + $mainloop.loop +end + +def test_outer + o = FObject["@outer + {0}"] + o.send_in 0, 25, 240, 320, 3, "#".intern, 42 + g = FObject["@global"] + g.send_in 0, :profiler_dump +end + +def test_jmax_to_pd filename + require "gridflow/extra/jmax_format.rb" + require "gridflow/extra/puredata_format.rb" + jfr = JMaxFileReader.new(File.open(filename),FObject) + FObject.broken_ok = true + my_patcher = jfr.parse +# my_patcher.subobjects.each {|x,| x.trigger if LoadBang===x } +# $mainloop.loop +# $p=my_patcher; ARGV.clear; load "/home/matju/bin/iruby" + filename = File.basename filename + filename[File.extname(filename)]=".pd" + filename[0,0]="pd_examples/" + pfw = PureDataFileWriter.new(filename) + pfw.write_patcher my_patcher + pfw.close +end + +def test_error + x = FObject["@store"] + x.send_in 0 +end + +if ARGV[0] then + name = ARGV.shift + send "test_#{name}", *ARGV +# ARGV.each {|a| send "test_#{a}" } + exit 0 +end + +#test_polygon +#test_math +#test_munchies +#test_image "grid file #{$imdir}/foo.grid" +#test_image "grid gzfile #{$imdir}/foo.grid.gz" +#test_print +#test_nonsense +#test_ppm2 +#test_anim "open file #{$imdir}/g001.jpg"#,"loop 0" +#test_anim "open ppm file #{$animdir}/b.ppm.cat" +#test_anim "open jpeg file #{$imdir}/rgb.jpeg.cat" +#test_anim "open quicktime file BLAH" +#test_anim "open quicktime file #{$imdir}/rgb_uncompressed.mov" +#test_anim "open quicktime file #{$imdir}/test_mjpega.mov" +#test_anim "open ppm gzfile motion_tracking.ppm.cat.gz" +#test_anim "open videodev /dev/video","channel 1","size 480 640" +#test_anim "open videodev /dev/video1 noinit","transfer read" +#test_anim "open videodev /dev/video","channel 1","size 120 160" +#test_anim "open mpeg file /home/matju/net/Animations/washington_zoom_in.mpeg" +#test_anim "open quicktime file /home/matju/Shauna/part_1.mov" +#test_anim "open quicktime file #{$imdir}/gt.mov" +#test_anim "open quicktime file /home/matju/pics/domopers_hi.mov" +#test_anim "open quicktime file /home/matju/net/c.mov" +#test_formats +#test_tcp +#test_sound +#test_metro +#$mainloop.loop diff --git a/externals/gridflow/bridge/placebo.rb b/externals/gridflow/bridge/placebo.rb new file mode 100644 index 00000000..3d712d1d --- /dev/null +++ b/externals/gridflow/bridge/placebo.rb @@ -0,0 +1,43 @@ +=begin + $Id: placebo.rb,v 1.1 2005-10-04 02:02:13 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 + +class Object + def self.dummy(sel) + self.module_eval "def #{sel}(*args) GridFlow.post \"dummy #{sel}: %s\", args.inspect end" + end +end + +module GridFlow + class<<self + # def add_creator_2(*args) post "dummy add_creator_2: %s", args.inspect end + dummy :add_creator_2 + def post_string(s) STDERR.puts s end + end + class Clock + def initialize(victim) @victim=victim end + dummy :delay + end + class Pointer + dummy :initialize + end +end diff --git a/externals/gridflow/bridge/puredata.c b/externals/gridflow/bridge/puredata.c new file mode 100644 index 00000000..dd6fd88f --- /dev/null +++ b/externals/gridflow/bridge/puredata.c @@ -0,0 +1,772 @@ +/* + $Id: puredata.c,v 1.1 2005-10-04 02:02:13 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. +*/ + +/* +'bridge_puredata.c' becomes 'gridflow.pd_linux' which loads Ruby and tells +Ruby to load the other 95% of Gridflow. GridFlow creates the FObject class +and then subclasses it many times, each time calling FObject.install(), +which tells the bridge to register a class in puredata. Puredata +objects have proxies for each of the non-first inlets so that any inlet +may receive any distinguished message. All inlets are typed as 'anything', +and the 'anything' method translates the whole message to Ruby objects and +tries to call a Ruby method of the proper name. +*/ + +#define IS_BRIDGE +#include "../base/grid.h.fcs" +/* resolving conflict: T_OBJECT will be PD's, not Ruby's */ +#undef T_OBJECT +#undef EXTERN +#include <m_pd.h> +#include <ctype.h> +#include <stdarg.h> +#include <unistd.h> +#include "g_canvas.h" + +/* **************************************************************** */ +struct BFObject; +struct FMessage { + BFObject *self; + int winlet; + t_symbol *selector; + int ac; + const t_atom *at; + bool is_init; +}; +#include <sys/time.h> +#include <signal.h> +#include <setjmp.h> +#define rb_sym_name rb_sym_name_r4j +static const char *rb_sym_name(Ruby sym) {return rb_id2name(SYM2ID(sym));} + +static BuiltinSymbols *syms; + +void CObject_freeee (void *victim) { + CObject *self = (CObject *)victim; + self->check_magic(); + if (!self->rself) { + fprintf(stderr,"attempt to free object that has no rself\n"); + abort(); + } + self->rself = 0; /* paranoia */ + delete self; +} + + +/* can't even refer to the other mGridFlow because we don't link + statically to the other gridflow.so */ +static Ruby mGridFlow2=0; +static Ruby mPointer=0; + +\class Pointer < CObject +struct Pointer : CObject { + void *p; + Pointer() { assert(!"DYING HORRIBLY"); } + Pointer(void *_p) : p(_p) {} + \decl Ruby ptr (); +}; +\def Ruby ptr () { return LONG2NUM(((long)p)); } +\classinfo { + IEVAL(rself, +"self.module_eval{" +"def inspect; p=('%08x'%ptr).gsub(/^\\.\\.f/,''); \"#<Pointer:#{p}>\" % ptr; end;" +"alias to_s inspect }" +);} +\end class Pointer +Ruby Pointer_s_new (void *ptr) { + return Data_Wrap_Struct(EVAL("GridFlow::Pointer"), 0, 0, new Pointer(ptr)); +} +void *Pointer_get (Ruby rself) { + DGS(Pointer); + return self->p; +} + +static Ruby make_error_message () { + char buf[1000]; + sprintf(buf,"%s: %s",rb_class2name(rb_obj_class(ruby_errinfo)), + rb_str_ptr(rb_funcall(ruby_errinfo,SI(to_s),0))); + Ruby ary = rb_ary_new2(2); + Ruby backtrace = rb_funcall(ruby_errinfo,SI(backtrace),0); + rb_ary_push(ary,rb_str_new2(buf)); + for (int i=0; i<2 && i<rb_ary_len(backtrace); i++) + rb_ary_push(ary,rb_funcall(backtrace,SI([]),1,INT2NUM(i))); +// rb_ary_push(ary,rb_funcall(rb_funcall(backtrace,SI(length),0),SI(to_s),0)); + return ary; +} + +static int ninlets_of (Ruby qlass) { + if (!rb_ivar_defined(qlass,SYM2ID(syms->iv_ninlets))) RAISE("no inlet count"); + return INT(rb_ivar_get(qlass,SYM2ID(syms->iv_ninlets))); +} + +static int noutlets_of (Ruby qlass) { + if (!rb_ivar_defined(qlass,SYM2ID(syms->iv_noutlets))) RAISE("no outlet count"); + return INT(rb_ivar_get(qlass,SYM2ID(syms->iv_noutlets))); +} + +#ifndef STACK_GROW_DIRECTION +#define STACK_GROW_DIRECTION -1 +#endif +extern "C" void Init_stack(VALUE *addr); +static VALUE *localize_sysstack () { + long bp; + sscanf(RUBY_STACK_END,"0x%08lx",&bp); + //fprintf(stderr,"old RUBY_STACK_END = %08lx\n",bp); + // HACK (2004.08.29: alx has a problem; i hope it doesn't get worse) + // this rounds to the last word of a 4k block + // cross fingers that no other OS does it too different + // !@#$ doesn't use STACK_GROW_DIRECTION + // bp=((bp+0xfff)&~0xfff)-sizeof(void*); + // GAAAH + bp=((bp+0xffff)&~0xffff)-sizeof(void*); + //fprintf(stderr,"new RUBY_STACK_END = %08lx\n",bp); + return (VALUE *)bp; +} + +//**************************************************************** +// BFObject + +struct BFObject : t_object { + int32 magic; // paranoia + Ruby rself; + int nin; // per object settings (not class) + int nout; // per object settings (not class) + t_outlet **out; + + void check_magic () { + if (magic != OBJECT_MAGIC) { + fprintf(stderr,"Object memory corruption! (ask the debugger)\n"); + for (int i=-1; i<=1; i++) { + fprintf(stderr,"this[0]=0x%08x\n",((int*)this)[i]); + } + raise(11); + } + } +}; + +static t_class *find_bfclass (t_symbol *sym) { + t_atom a[1]; + SETSYMBOL(a,sym); + char buf[4096]; + if (sym==&s_list) strcpy(buf,"list"); else atom_string(a,buf,sizeof(buf)); + Ruby v = rb_hash_aref(rb_ivar_get(mGridFlow2, SI(@fclasses)), rb_str_new2(buf)); + if (v==Qnil) { + post("GF: class not found: '%s'",buf); + return 0; + } + if (Qnil==rb_ivar_get(v,SI(@bfclass))) { + post("@bfclass missing for '%s'",buf); + return 0; + } + return FIX2PTR(t_class,rb_ivar_get(v,SI(@bfclass))); +} + +static t_class *BFProxy_class; + +struct BFProxy : t_object { + BFObject *parent; + int inlet; +}; + +static void Bridge_export_value(Ruby arg, t_atom *at) { + if (INTEGER_P(arg)) { + SETFLOAT(at,NUM2INT(arg)); + } else if (SYMBOL_P(arg)) { + const char *name = rb_sym_name(arg); + SETSYMBOL(at,gensym((char *)name)); + } else if (FLOAT_P(arg)) { + SETFLOAT(at,RFLOAT(arg)->value); + } else if (rb_obj_class(arg)==mPointer) { + SETPOINTER(at,(t_gpointer*)Pointer_get(arg)); + } else { + RAISE("cannot convert argument of class '%s'", + rb_str_ptr(rb_funcall(rb_funcall(arg,SI(class),0),SI(inspect),0))); + } +} + +static Ruby Bridge_import_value(const t_atom *at) { + t_atomtype t = at->a_type; + if (t==A_SYMBOL) { + return ID2SYM(rb_intern(at->a_w.w_symbol->s_name)); + } else if (t==A_FLOAT) { + return rb_float_new(at->a_w.w_float); + } else if (t==A_POINTER) { + return Pointer_s_new(at->a_w.w_gpointer); + } else { + return Qnil; /* unknown */ + } +} + +static Ruby BFObject_method_missing_1 (FMessage *fm) { + Ruby argv[fm->ac+2]; + argv[0] = INT2NUM(fm->winlet); + argv[1] = ID2SYM(rb_intern(fm->selector->s_name)); + for (int i=0; i<fm->ac; i++) argv[2+i] = Bridge_import_value(fm->at+i); + fm->self->check_magic(); + rb_funcall2(fm->self->rself,SI(send_in),fm->ac+2,argv); + return Qnil; +} + +static Ruby BFObject_rescue (FMessage *fm) { + Ruby error_array = make_error_message(); +// for (int i=0; i<rb_ary_len(error_array); i++) +// post("%s\n",rb_str_ptr(rb_ary_ptr(error_array)[i])); + if (fm->self) pd_error(fm->self,"%s",rb_str_ptr( + rb_funcall(error_array,SI(join),1,rb_str_new2("\n")))); + if (fm->self && fm->is_init) fm->self = 0; + return Qnil; +} + +static void BFObject_method_missing (BFObject *bself, +int winlet, t_symbol *selector, int ac, t_atom *at) { + FMessage fm = { bself, winlet, selector, ac, at, false }; + if (!bself->rself) { + pd_error(bself,"message to a dead object. (supposed to be impossible)"); + return; + } + rb_rescue2( + (RMethod)BFObject_method_missing_1,(Ruby)&fm, + (RMethod)BFObject_rescue,(Ruby)&fm, + rb_eException,0); +} + +static void BFObject_method_missing0 (BFObject *self, +t_symbol *s, int argc, t_atom *argv) { + BFObject_method_missing(self,0,s,argc,argv); +} + +static void BFProxy_method_missing(BFProxy *self, +t_symbol *s, int argc, t_atom *argv) { + BFObject_method_missing(self->parent,self->inlet,s,argc,argv); +} + +static Ruby BFObject_init_1 (FMessage *fm) { + Ruby argv[fm->ac+1]; + for (int i=0; i<fm->ac; i++) argv[i+1] = Bridge_import_value(fm->at+i); + + if (fm->selector==&s_list) { + argv[0] = rb_str_new2("list"); // pd is slightly broken here + } else { + argv[0] = rb_str_new2(fm->selector->s_name); + } + + Ruby rself = rb_funcall2(rb_const_get(mGridFlow2,SI(FObject)),SI([]),fm->ac+1,argv); + DGS(FObject); + self->bself = fm->self; + self->bself->rself = rself; + + int ninlets = self->bself->nin = ninlets_of(rb_funcall(rself,SI(class),0)); + int noutlets = self->bself->nout = noutlets_of(rb_funcall(rself,SI(class),0)); + + for (int i=1; i<ninlets; i++) { + BFProxy *p = (BFProxy *)pd_new(BFProxy_class); + p->parent = self->bself; + p->inlet = i; + inlet_new(self->bself, &p->ob_pd, 0,0); + } + self->bself->out = new t_outlet*[noutlets]; + for (int i=0; i<noutlets; i++) { + self->bself->out[i] = outlet_new(self->bself,&s_anything); + } + rb_funcall(rself,SI(initialize2),0); + return rself; +} + +static void *BFObject_init (t_symbol *classsym, int ac, t_atom *at) { + t_class *qlass = find_bfclass(classsym); + if (!qlass) return 0; + BFObject *bself = (BFObject *)pd_new(qlass); + bself->magic = OBJECT_MAGIC; + bself->check_magic(); + FMessage fm = { self: bself, winlet:-1, selector: classsym, + ac: ac, at: at, is_init: true }; + long r = rb_rescue2( + (RMethod)BFObject_init_1,(Ruby)&fm, + (RMethod)BFObject_rescue,(Ruby)&fm, + rb_eException,0); + return r==Qnil ? 0 : (void *)bself; // return NULL if broken object +} + +static void BFObject_delete_1 (FMessage *fm) { + fm->self->check_magic(); + if (fm->self->rself) { + rb_funcall(fm->self->rself,SI(delete),0); + } else { + post("BFObject_delete is NOT handling BROKEN object at %08x",(int)fm); + } +} + +static void BFObject_delete (BFObject *bself) { + bself->check_magic(); + FMessage fm = { self: bself, winlet:-1, selector: gensym("delete"), + ac: 0, at: 0, is_init: false }; + rb_rescue2( + (RMethod)BFObject_delete_1,(Ruby)&fm, + (RMethod)BFObject_rescue,(Ruby)&fm, + rb_eException,0); + bself->magic = 0xDeadBeef; +} + +/* **************************************************************** */ + +struct RMessage { + VALUE rself; + ID sel; + int argc; + VALUE *argv; +}; + +// this was called rb_funcall_rescue[...] but recently (ruby 1.8.2) +// got a conflict with a new function in ruby. + +VALUE rb_funcall_myrescue_1(RMessage *rm) { + return rb_funcall2(rm->rself,rm->sel,rm->argc,rm->argv); +} + +static Ruby rb_funcall_myrescue_2 (RMessage *rm) { + Ruby error_array = make_error_message(); +// for (int i=0; i<rb_ary_len(error_array); i++) +// post("%s\n",rb_str_ptr(rb_ary_ptr(error_array)[i])); + post("%s",rb_str_ptr(rb_funcall(error_array,SI(join),1,rb_str_new2("\n")))); + return Qnil; +} + +VALUE rb_funcall_myrescue(VALUE rself, ID sel, int argc, ...) { + va_list foo; + va_start(foo,argc); + VALUE argv[argc]; + for (int i=0; i<argc; i++) argv[i] = va_arg(foo,VALUE); + RMessage rm = { rself, sel, argc, argv }; + va_end(foo); + return rb_rescue2( + (RMethod)rb_funcall_myrescue_1,(Ruby)&rm, + (RMethod)rb_funcall_myrescue_2,(Ruby)&rm, + rb_eException,0); +} + +/* Call this to get a gobj's bounding rectangle in pixels */ +void bf_getrectfn(t_gobj *x, struct _glist *glist, +int *x1, int *y1, int *x2, int *y2) { + BFObject *bself = (BFObject*)x; + Ruby can = PTR2FIX(glist_getcanvas(glist)); + Ruby a = rb_funcall_myrescue(bself->rself,SI(pd_getrect),1,can); + if (TYPE(a)!=T_ARRAY || rb_ary_len(a)<4) { + post("bf_getrectfn: return value should be 4-element array"); + *x1=*y1=*x2=*y2=0; + return; + } + *x1 = INT(rb_ary_ptr(a)[0]); + *y1 = INT(rb_ary_ptr(a)[1]); + *x2 = INT(rb_ary_ptr(a)[2]); + *y2 = INT(rb_ary_ptr(a)[3]); +} + +/* and this to displace a gobj: */ +void bf_displacefn(t_gobj *x, struct _glist *glist, int dx, int dy) { + Ruby can = PTR2FIX(glist_getcanvas(glist)); + BFObject *bself = (BFObject *)x; + bself->te_xpix+=dx; + bself->te_ypix+=dy; + rb_funcall_myrescue(bself->rself,SI(pd_displace),3,can,INT2NUM(dx),INT2NUM(dy)); + canvas_fixlinesfor(glist, (t_text *)x); +} + +/* change color to show selection: */ +void bf_selectfn(t_gobj *x, struct _glist *glist, int state) { + Ruby can = PTR2FIX(glist_getcanvas(glist)); + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_select),2,can,INT2NUM(state)); +} + +/* change appearance to show activation/deactivation: */ +void bf_activatefn(t_gobj *x, struct _glist *glist, int state) { + Ruby can = PTR2FIX(glist_getcanvas(glist)); + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_activate),2,can,INT2NUM(state)); +} + +/* warn a gobj it's about to be deleted */ +void bf_deletefn(t_gobj *x, struct _glist *glist) { + Ruby can = PTR2FIX(glist_getcanvas(glist)); + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_delete),1,can); + canvas_deletelinesfor(glist, (t_text *)x); +} + +/* making visible or invisible */ +void bf_visfn(t_gobj *x, struct _glist *glist, int flag) { + Ruby can = PTR2FIX(glist_getcanvas(glist)); + Ruby rself = ((BFObject*)x)->rself; + DGS(FObject); + self->check_magic(); + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_vis),2,can,INT2NUM(flag)); +} + +/* field a mouse click (when not in "edit" mode) */ +int bf_clickfn(t_gobj *x, struct _glist *glist, +int xpix, int ypix, int shift, int alt, int dbl, int doit) { + Ruby can = PTR2FIX(glist_getcanvas(glist)); + Ruby ret = rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_click),7,can, + INT2NUM(xpix),INT2NUM(ypix), + INT2NUM(shift),INT2NUM(alt), + INT2NUM(dbl),INT2NUM(doit)); + if (TYPE(ret) == T_FIXNUM) return INT(ret); + post("bf_clickfn: expected Fixnum"); + return 0; +} + +/* save to a binbuf */ +void bf_savefn(t_gobj *x, t_binbuf *b) { + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_save),1,Qnil); +} + +/* open properties dialog */ +void bf_propertiesfn(t_gobj *x, struct _glist *glist) { + Ruby can = PTR2FIX(glist_getcanvas(glist)); + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_properties),1,can); +} + +/* get keypresses during focus */ +void bf_keyfn(void *x, t_floatarg fkey) { + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_key),1,INT2NUM((int)fkey)); +} + +/* get motion diff during focus */ +void bf_motionfn(void *x, t_floatarg dx, t_floatarg dy) { + rb_funcall_myrescue(((BFObject*)x)->rself,SI(pd_motion),2, + INT2NUM((int)dx), INT2NUM((int)dy)); +} + +/* **************************************************************** */ + +static void BFObject_class_init_1 (t_class *qlass) { + class_addanything(qlass,(t_method)BFObject_method_missing0); +} + +\class FObject + +static Ruby FObject_s_install2(Ruby rself, Ruby name) { + if (TYPE(name)!=T_STRING) RAISE("name must be String"); + t_class *qlass = class_new(gensym(rb_str_ptr(name)), + (t_newmethod)BFObject_init, (t_method)BFObject_delete, + sizeof(BFObject), CLASS_DEFAULT, A_GIMME,0); + rb_ivar_set(rself, SI(@bfclass), PTR2FIX(qlass)); + FMessage fm = {0, -1, 0, 0, 0, false}; + rb_rescue2( + (RMethod)BFObject_class_init_1,(Ruby)qlass, + (RMethod)BFObject_rescue,(Ruby)&fm, + rb_eException,0); + return Qnil; +} + +static Ruby FObject_send_out2(int argc, Ruby *argv, Ruby rself) { +//\def Ruby send_out2(...) { + DGS(FObject); + BFObject *bself = self->bself; + if (!bself) { + //post("FObject#send_out2 : bself is NULL, rself=%08x",rself); + return Qnil; + } + bself->check_magic(); + Ruby qlass = rb_funcall(rself,SI(class),0); + int outlet = INT(argv[0]); + Ruby sym = argv[1]; + argc-=2; + argv+=2; + t_atom sel, at[argc]; + Bridge_export_value(sym,&sel); + for (int i=0; i<argc; i++) Bridge_export_value(argv[i],at+i); + t_outlet *out = bself->te_outlet; + outlet_anything(bself->out[outlet],atom_getsymbol(&sel),argc,at); + return Qnil; +} + +static Ruby FObject_s_set_help (Ruby rself, Ruby path) { + path = rb_funcall(path,SI(to_s),0); + Ruby qlassid = rb_ivar_get(rself,SI(@bfclass)); + if (qlassid==Qnil) RAISE("@bfclass==nil ???"); + t_class *qlass = FIX2PTR(t_class,qlassid); + class_sethelpsymbol(qlass,gensym(rb_str_ptr(path))); + return Qnil; +} + +static Ruby GridFlow_s_gui (int argc, Ruby *argv, Ruby rself) { + if (argc!=1) RAISE("bad args"); + Ruby command = rb_funcall(argv[0],SI(to_s),0); + sys_gui(rb_str_ptr(command)); + return Qnil; +} + +static Ruby GridFlow_s_bind (Ruby rself, Ruby argv0, Ruby argv1) { + if (TYPE(argv0)==T_STRING) { +#if PD_VERSION_INT < 37 + RAISE("requires Pd 0.37"); +#else + Ruby name = rb_funcall(argv0,SI(to_s),0); + Ruby qlassid = rb_ivar_get(rb_hash_aref(rb_ivar_get(mGridFlow2,SI(@fclasses)),name),SI(@bfclass)); + if (qlassid==Qnil) RAISE("no such class: %s",rb_str_ptr(name)); + pd_typedmess(&pd_objectmaker,gensym(rb_str_ptr(name)),0,0); + t_pd *o = pd_newest(); + pd_bind(o,gensym(rb_str_ptr(argv1))); +#endif + } else { + Ruby rself = argv0; + DGS(FObject); + t_symbol *s = gensym(rb_str_ptr(argv1)); + t_pd *o = (t_pd *)(self->bself); + //fprintf(stderr,"binding %08x to: \"%s\" (%08x %s)\n",o,rb_str_ptr(argv[1]),s,s->s_name); + pd_bind(o,s); + } + return Qnil; +} + +static Ruby FObject_s_gui_enable (Ruby rself) { + Ruby qlassid = rb_ivar_get(rself,SI(@bfclass)); + if (qlassid==Qnil) RAISE("no class id ?"); + t_widgetbehavior *wb = new t_widgetbehavior; + wb->w_getrectfn = bf_getrectfn; + wb->w_displacefn = bf_displacefn; + wb->w_selectfn = bf_selectfn; + wb->w_activatefn = bf_activatefn; + wb->w_deletefn = bf_deletefn; + wb->w_visfn = bf_visfn; + wb->w_clickfn = bf_clickfn; + //wb->w_savefn = bf_savefn; + //wb->w_propertiesfn = bf_propertiesfn; + class_setwidget(FIX2PTR(t_class,qlassid),wb); + return Qnil; +} + +static Ruby FObject_focus (Ruby rself, Ruby canvas_, Ruby x_, Ruby y_) { + DGS(FObject); + t_glist *canvas = FIX2PTR(t_glist,canvas_); + t_gobj *bself = (t_gobj *)(self->bself); + int x = INT(x_); + int y = INT(y_); + glist_grab(canvas,bself, bf_motionfn, bf_keyfn, x,y); + return Qnil; +} + +// doesn't use rself but still is aside FObject_focus for symmetry reasons. +static Ruby FObject_unfocus (Ruby rself, Ruby canvas_) { + DGS(FObject); + t_glist *canvas = FIX2PTR(t_glist,canvas_); + glist_grab(canvas,0,0,0,0,0); + return Qnil; +} + +static Ruby FObject_add_inlets (Ruby rself, Ruby n_) { + DGS(FObject); + if (!self->bself) RAISE("there is no bself"); + int n = INT(n_); + for (int i=self->bself->nin; i<self->bself->nin+n; i++) { + BFProxy *p = (BFProxy *)pd_new(BFProxy_class); + p->parent = self->bself; + p->inlet = i; + inlet_new(self->bself, &p->ob_pd, 0,0); + } + self->bself->nin+=n; + return Qnil; +} + +static Ruby FObject_add_outlets (Ruby rself, Ruby n_) { + DGS(FObject); + if (!self->bself) RAISE("there is no bself"); + int n = INT(n_); + t_outlet **oldouts = self->bself->out; + self->bself->out = new t_outlet*[self->bself->nout+n]; + memcpy(self->bself->out,oldouts,self->bself->nout*sizeof(t_outlet*)); + for (int i=self->bself->nout; i<self->bself->nout+n; i++) { + self->bself->out[i] = outlet_new(self->bself,&s_anything); + } + self->bself->nout+=n; + return Qnil; +} + +static Ruby bridge_add_to_menu (int argc, Ruby *argv, Ruby rself) { + if (argc!=1) RAISE("bad args"); + Ruby name = rb_funcall(argv[0],SI(to_s),0); + Ruby qlassid = rb_ivar_get(rb_hash_aref(rb_ivar_get(mGridFlow2,SI(@fclasses)),name),SI(@bfclass)); + if (qlassid==Qnil) RAISE("no such class: %s",rb_str_ptr(name)); + //!@#$ + return Qnil; +} + +static Ruby GridFlow_s_add_creator_2 (Ruby rself, Ruby name_) { + t_symbol *name = gensym(rb_str_ptr(rb_funcall(name_,SI(to_s),0))); + class_addcreator((t_newmethod)BFObject_init,name,A_GIMME,0); + return Qnil; +} + +static Ruby FObject_get_position (Ruby rself, Ruby canvas) { + DGS(FObject); + t_text *bself = (t_text *)(self->bself); + t_glist *c = FIX2PTR(t_glist,canvas); + float x0,y0; + if (c->gl_havewindow || !c->gl_isgraph) { + x0=bself->te_xpix; + y0=bself->te_ypix; + } + else { // graph-on-parent: have to zoom + float zx = float(c->gl_x2 - c->gl_x1) / (c->gl_screenx2 - c->gl_screenx1); + float zy = float(c->gl_y2 - c->gl_y1) / (c->gl_screeny2 - c->gl_screeny1); + x0 = glist_xtopixels(c, c->gl_x1 + bself->te_xpix*zx); + y0 = glist_ytopixels(c, c->gl_y1 + bself->te_ypix*zy); + } + Ruby a = rb_ary_new(); + rb_ary_push(a,INT2NUM((int)x0)); + rb_ary_push(a,INT2NUM((int)y0)); + return a; +} + +\classinfo {} +\end class FObject + +//**************************************************************** + +\class Clock < CObject +struct Clock : CObject { + t_clock *serf; + Ruby owner; /* copy of ptr that serf already has, for marking */ + \decl void set (double systime); + \decl void delay(double delaytime); + \decl void unset(); +}; + +void Clock_fn (Ruby rself) { rb_funcall_myrescue(rself,SI(call),0); } +void Clock_mark (Clock *self) { rb_gc_mark(self->owner); } +void Clock_free (Clock *self) { clock_free(self->serf); CObject_freeee(self); } + +Ruby Clock_s_new (Ruby qlass, Ruby owner) { + Clock *self = new Clock(); + self->rself = Data_Wrap_Struct(qlass, Clock_mark, Clock_free, self); + self->serf = clock_new((void*)owner,(t_method)Clock_fn); + self->owner = owner; + return self->rself; +} + +\def void set (double systime) { clock_set(serf, systime); } +\def void delay(double delaytime) { clock_delay(serf,delaytime); } +\def void unset() { clock_unset(serf); } + +\classinfo {} +\end class Clock + +//**************************************************************** + +Ruby GridFlow_s_post_string (Ruby rself, Ruby string) { + if (TYPE(string)!=T_STRING) RAISE("not a string!"); + post("%s",rb_str_ptr(string)); + return Qnil; +} + +#define SDEF(_name1_,_name2_,_argc_) \ + rb_define_singleton_method(mGridFlow2,_name1_,(RMethod)GridFlow_s_##_name2_,_argc_) + +Ruby gf_bridge_init (Ruby rself) { + Ruby ver = EVAL("GridFlow::GF_VERSION"); + if (strcmp(rb_str_ptr(ver), GF_VERSION) != 0) { + RAISE("GridFlow version mismatch: " + "main library is '%s'; bridge is '%s'", + rb_str_ptr(ver), GF_VERSION); + } + syms = FIX2PTR(BuiltinSymbols,rb_ivar_get(mGridFlow2,SI(@bsym))); + Ruby fo = EVAL("GridFlow::FObject"); + rb_define_singleton_method(fo,"install2",(RMethod)FObject_s_install2,1); + rb_define_singleton_method(fo,"gui_enable", (RMethod)FObject_s_gui_enable, 0); + rb_define_singleton_method(fo,"set_help", (RMethod)FObject_s_set_help, 1); + rb_define_method(fo,"get_position",(RMethod)FObject_get_position,1); + rb_define_method(fo,"send_out2", (RMethod)FObject_send_out2,-1); + rb_define_method(fo,"send_out2", (RMethod)FObject_send_out2,-1); + rb_define_method(fo,"add_inlets", (RMethod)FObject_add_inlets, 1); + rb_define_method(fo,"add_outlets", (RMethod)FObject_add_outlets, 1); + rb_define_method(fo,"unfocus", (RMethod)FObject_unfocus, 1); + rb_define_method(fo, "focus", (RMethod)FObject_focus, 3); + + SDEF("post_string",post_string,1); + SDEF("add_creator_2",add_creator_2,1); + SDEF("gui",gui,-1); + SDEF("bind",bind,2); + // SDEF("add_to_menu",add_to_menu,-1); + + \startall + rb_define_singleton_method(EVAL("GridFlow::Clock" ),"new", (RMethod)Clock_s_new, 1); + rb_define_singleton_method(EVAL("GridFlow::Pointer"),"new", (RMethod)Pointer_s_new, 1); + mPointer = EVAL("GridFlow::Pointer"); + EVAL("class<<GridFlow;attr_accessor :config; end"); + EVAL("GridFlow.config = {'PUREDATA_PATH' => %{"PUREDATA_PATH"}}"); + return Qnil; +} + +struct BFGridFlow : t_object {}; + +t_class *bindpatcher; +static void *bindpatcher_init (t_symbol *classsym, int ac, t_atom *at) { + t_pd *bself = pd_new(bindpatcher); + if (ac!=1 || at->a_type != A_SYMBOL) { + post("bindpatcher: oops"); + } else { + t_symbol *s = atom_getsymbol(at); + post("binding patcher to: %s",s->s_name); + pd_bind((t_pd *)canvas_getcurrent(),s); + } + return bself; +} + +extern "C" void gridflow_setup () { + char *foo[] = {"Ruby-for-PureData","-w","-e",";"}; + post("setting up Ruby-for-PureData..."); +/* + post("pd_getfilename() = %s", pd_getfilename()->s_name); + post("pd_getdirname() = %s", pd_getdirname()->s_name); + post("canvas_getcurrentdir() = %p", canvas_getcurrentdir()); + char *dirresult = new char[242]; + char *nameresult; + int fd = open_via_path("","gridflow",".so",dirresult,&nameresult,242,1); + post("open_via_path: fd=%d dirresult=\"%s\" nameresult=\"%s\"",fd,dirresult,nameresult); + delete[] dirresult; +*/ + ruby_init(); + Init_stack(localize_sysstack()); + ruby_options(COUNT(foo),foo); + post("we are using Ruby version %s",rb_str_ptr(EVAL("RUBY_VERSION"))); + Ruby cData = rb_const_get(rb_cObject,SI(Data)); + BFProxy_class = class_new(gensym("ruby_proxy"), + NULL,NULL,sizeof(BFProxy),CLASS_PD|CLASS_NOINLET, A_NULL); + class_addanything(BFProxy_class,BFProxy_method_missing); + rb_define_singleton_method(cData,"gf_bridge_init", + (RMethod)gf_bridge_init,0); + + mGridFlow2 = EVAL( + "module GridFlow; class<<self; attr_reader :bridge_name; end; " + "@bridge_name = 'puredata'; self end"); + post("(done)"); + if (! + EVAL("begin require 'gridflow'; true; rescue Exception => e;\ + STDERR.puts \"[#{e.class}] [#{e.message}]:\n#{e.backtrace.join'\n'}\"; false; end")) + { + post("ERROR: Cannot load GridFlow-for-Ruby (gridflow.so)\n"); + return; + } + bindpatcher = class_new(gensym("bindpatcher"), + (t_newmethod)bindpatcher_init, 0, sizeof(t_object),CLASS_DEFAULT,A_GIMME,0); +} + + diff --git a/externals/gridflow/bridge/puredata.rb b/externals/gridflow/bridge/puredata.rb new file mode 100644 index 00000000..fffda1c2 --- /dev/null +++ b/externals/gridflow/bridge/puredata.rb @@ -0,0 +1,194 @@ +=begin + $Id: puredata.rb,v 1.1 2005-10-04 02:02:13 matju Exp $ + + GridFlow + Copyright (c) 2001,2002 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 + +# <matju> alx1: in puredata.rb, just after the header, you have a %w() block, +# and in it you write the name of your object, and if your helpfile is not +# named like your object, then you add an equal sign and the filename + +#!@#$ DON'T PUT ABSTRACTIONS IN THE %w() !!! +# @mouse=help_mouse @motion_detection=help_motion_detect @fade=help_fade +# @apply_colormap_channelwise @checkers @complex_sq @contrast +# @posterize @ravel @greyscale_to_rgb @rgb_to_greyscale @solarize @spread +#rgb_to_yuv=#rgb_to_yuv_and_#yuv_to_rgb +#yuv_to_rgb=#rgb_to_yuv_and_#yuv_to_rgb +#clip #contrast #fade #numop #remap_image + +# NEW help files +#!@#$ (what's #+-help.pd ? #print-help2.pd ?) +%w( + # #cast #dim #reverse + #pack=#unpack-#pack + #unpack=#unpack-#pack + renamefile + #in plotter_control + listelement exec ls #print unix_time +).each {|name| + if name =~ /=/ then name,file = name.split(/=/) else file = name end + begin + x = GridFlow.fclasses[name] + x.set_help "gridflow/flow_classes/#{file}-help.pd" + rescue Exception => e + GridFlow.post "for [#{name}], #{e.class}: #{e}" # + ":\n" + e.backtrace.join("\n") + end +} + +# OLD help files +%w( + @cast + @convolve @downscale_by @draw_polygon @export=@importexport + @finished @fold @for @global @grade + @import=@importexport @inner=@foldinnerouter + @in=@inout @join @layer @outer=@foldinnerouter @out=@inout + @! @perspective printargs @print @redim + rubyprint @scale_by @scale_to @scan + @store +).each {|name| + if name =~ /=/ then name,file = name.split(/=/) else file = name end + begin + GridFlow.fclasses[name].set_help "gridflow/#{file}.pd" + rescue Exception => e + GridFlow.post "ruby #{e.class}: #{e}:\n" + e.backtrace.join("\n") + end +} + +#GridFlow.gui "frame .controls.gridflow -relief ridge -borderwidth 2\n" +#GridFlow.gui "button .controls.gridflow.button -text FOO\n" +#GridFlow.gui "pack .controls.gridflow.button -side left\n" +#GridFlow.gui "pack .controls.gridflow -side right\n" + +GridFlow.gui %q{ + +if {[catch { + # pd 0.37 + menu .mbar.gridflow -tearoff $pd_tearoff + .mbar add cascade -label "GridFlow" -menu .mbar.gridflow + set gfmenu .mbar.gridflow +}]} { + # pd 0.36 + ###the problem is that GridFlow.bind requires 0.37 +} +catch { +$gfmenu add command -label "profiler_dump" -command {pd "gridflow profiler_dump;"} +$gfmenu add command -label "profiler_reset" -command {pd "gridflow profiler_reset;"} +$gfmenu add command -label "formats" -command {pd "gridflow formats;"} +} + +if {[string length [info command post]] == 0} { + proc post {x} {puts $x} +} + +# if not running Impd: +if {[string length [info command listener_new]] == 0} { +# start of part duplicated from Impd +proc listener_new {self name} { + global _ + set _($self:hist) {} + set _($self:histi) 0 + frame $self + label $self.label -text "$name: " + entry $self.entry -width 40 +# entry $self.count -width 5 + pack $self.label -side left + pack $self.entry -side left -fill x -expand yes +# pack $self.count -side left + pack $self -fill x -expand no + bind $self.entry <Up> "listener_up $self" + bind $self.entry <Down> "listener_down $self" +} + +proc listener_up {self} { + global _ + if {$_($self:histi) > 0} {set _($self:histi) [expr -1+$_($self:histi)]} + $self.entry delete 0 end + $self.entry insert 0 [lindex $_($self:hist) $_($self:histi)] + $self.entry icursor end +# $self.count delete 0 end +# $self.count insert 0 "$_($self:histi)/[llength $_($self:hist)]" +} + +proc listener_down {self} { + global _ + if {$_($self:histi) < [llength $_($self:hist)]} {incr _($self:histi)} + $self.entry delete 0 end + $self.entry insert 0 [lindex $_($self:hist) $_($self:histi)] + $self.entry icursor end +# $self.count delete 0 end +# $self.count insert 0 "$_($self:histi)/[llength $_($self:hist)]" +} + +proc listener_append {self v} { + global _ + lappend _($self:hist) $v + set _($self:histi) [llength $_($self:hist)] +} + +proc tcl_eval {} { + set l [.tcl.entry get] + post "tcl: $l" + post "returns: [eval $l]" + listener_append .tcl [.tcl.entry get] + .tcl.entry delete 0 end + +} +if {[catch { + listener_new .tcl "Tcl" + bind .tcl.entry <Return> {tcl_eval} +}]} { + listener_new .tcl "Tcl" {tcl_eval} +} + + +} +# end of part duplicated from Impd + +proc ruby_eval {} { + set l {} + foreach c [split [.ruby.entry get] ""] {lappend l [scan $c %c]} + pd "gridflow eval $l;" + listener_append .ruby [.ruby.entry get] + .ruby.entry delete 0 end +} + +if {[catch { + listener_new .ruby "Ruby" + bind .ruby.entry <Return> {ruby_eval} +}]} { + listener_new .ruby "Ruby" {ruby_eval} +} + +} # GridFlow.gui + +if false +GridFlow.gui %q{ +catch { + if {[file exists ${pd_guidir}/lib/gridflow/icons/peephole.gif]} { + global pd_guidir + image create photo icon_peephole -file ${pd_guidir}/lib/gridflow/icons/peephole.gif + global butt + button_bar_add peephole {guiext peephole} + } { + puts $stderr GAAAH + } +} +} # GridFlow.gui +end diff --git a/externals/gridflow/cpu/mmx.rb b/externals/gridflow/cpu/mmx.rb new file mode 100644 index 00000000..1a3b15d3 --- /dev/null +++ b/externals/gridflow/cpu/mmx.rb @@ -0,0 +1,225 @@ +=begin + $Id: mmx.rb,v 1.1 2005-10-04 02:02:14 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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 + +STDOUT.reopen ARGV[0], "w" +$loader = File.open ARGV[1], "w" +$count = 0 +$lines = 0 + +puts "; generated by/for GridFlow 0.8.0" +$loader.puts "#include \"../base/grid.h.fcs\"\nextern \"C\" {" + +# this class is not really used yet (only self.make) +class AsmFunction + def initialize(name) + @name = name + @label_count = 1 + end + def self.make(name) + puts "", "GLOBAL #{name}", "#{name}:" + puts "push ebp", "mov ebp,esp", "push esi", "push edi" + yield AsmFunction.new(name) + puts "pop edi", "pop esi", "leave", "ret", "" + end + def make_until(*ops) + a = @label_count + b = @label_count+1 + @label_count+=2 + ops[-1]<<" #{@name}_#{b}" + puts "#{@name}_#{a}: ", *ops + yield + puts "jmp #{@name}_#{a}" + puts "#{@name}_#{b}:" + end +end + +$sizeof = { + :uint8 => 1, + :int16 => 2, + :int32 => 4, + :int64 => 8, + :float32 => 4, + :float64 => 8, +} + +$accum = { + :uint8 => "al", + :int16 => "ax", + :int32 => "eax", +} + +$asm_type = { + :uint8 => "byte", + :int16 => "word", + :int32 => "dword", + :int64 => "qword", +} + +# in the following, the opcode "_" means no such thing seems available. +# also >> for x86 ought to be shr in the uint8 case. +# btw, i got all of the MMX information from the NASM manual, Appendix B. +$opcodes = { +# [--GF--|--x86--|--mmx-et-al----------------------------------------] +# [ | |-uint8-|-int16-|-int32-|-int64-|-float32-|-float64-] + :add => %w[ + add paddb paddw paddd paddq ], + :sub => %w[ - sub psubb psubw psubd psubq ], + :and => %w[ & and pand pand pand pand ], + :xor => %w[ ^ xor pxor pxor pxor pxor ], + :or => %w[ | or por por por por ], +# :max => %w[ max _ pmaxub pmaxsw _ _ ], # not plain MMX !!! (req.Katmai) +# :min => %w[ min _ pminub pminsw _ _ ], # not plain MMX !!! (req.Katmai) +# :eq => %w[ == _ pcmpeqb pcmpeqw pcmpeqd _ ], +# :gt => %w[ > _ pcmpgtb pcmpgtw pcmpgtd _ ], +# :shl => %w[ << shl _ psllw pslld psllq ], # noncommutative +# :shr => %w[ >> sar _ psraw psrad _ ], # noncommutative +# :clipadd => %w[ clip+ _ paddusb paddsw _ _ ], # future use +# :clipsub => %w[ clip- _ psubusb psubsw _ _ ], # future use +# :andnot => %w[ ¬ _ pandn pandn pandn pandn ], # not planned +} + +$opcodes.each {|k,op| + op.map! {|x| if x=="_" then nil else x end } + STDERR.puts op.inspect +} + +$decls = "" +$install = "" + +def make_fun_map(op,type) + s="mmx_#{type}_map_#{op}" + size = $sizeof[type] + accum = $accum[type] + sym = $opcodes[op][0] + opcode = $opcodes[op][1] + mopcode = $opcodes[op][size+(size<4 ? 1 : 0)] + return if not mopcode + AsmFunction.make(s) {|a| + puts "mov ecx,[ebp+8]", "mov esi,[ebp+12]", "mov eax,[ebp+16]" + puts "mov dx,ax", "shl eax,8", "mov al,dl" if size==1 + puts "mov edx,eax", "shl eax,16", "mov ax,dx" if size<=2 + puts "push eax", "push eax", "movq mm7,[esp]", "add esp,8" + foo = proc {|n| + a.make_until("cmp ecx,#{8/size*n}","jb near") { + 0.step(n,4) {|k| + nn=[n-k,4].min + o=(0..3).map{|x| 8*(x+k) } + for i in 0...nn do puts "movq mm#{i},[esi+#{o[i]}]" end + for i in 0...nn do puts "#{mopcode} mm#{i},mm7" end + for i in 0...nn do puts "movq [esi+#{o[i]}],mm#{i}" end + } + puts "lea esi,[esi+#{8*n}]", "lea ecx,[ecx-#{8/size*n}]" + } + } + foo.call 4 + foo.call 1 + a.make_until("test ecx,ecx", "jz") { + puts "#{opcode} #{$asm_type[type]} [esi],#{accum}", "lea esi,[esi+#{size}]" + puts "dec ecx" + } + puts "emms" + } + $decls << "void #{s}(int,#{type}*,#{type});\n" + $install << "FIX2PTR(Numop,rb_hash_aref(op_dict,SYM(#{sym})))" + $install << "->on_#{type}.op_map = #{s};\n" + $count += 1 +end + +def make_fun_zip(op,type) +s="mmx_#{type}_zip_#{op}" + size = $sizeof[type] + accum = $accum[type] + sym = $opcodes[op][0] + opcode = $opcodes[op][1] + mopcode = $opcodes[op][size+(size<4 ? 1 : 0)] + return if not mopcode + AsmFunction.make(s) {|a| + puts "mov ecx,[ebp+8]", "mov edi,[ebp+12]", + "mov esi,[ebp+16]"#, "mov ebx,[ebp+20]" + foo = proc {|n| + a.make_until("cmp ecx,#{8/size*n}","jb near") { + 0.step(n,4) {|k| + nn=[n-k,4].min + o=(0..3).map{|x| 8*(x+k) } + for i in 0...nn do puts "movq mm#{i},[edi+#{o[i]}]" end + for i in 0...nn do puts "movq mm#{i+4},[esi+#{o[i]}]" end + for i in 0...nn do puts "#{mopcode} mm#{i},mm#{i+4}" end + for i in 0...nn do puts "movq [edi+#{o[i]}],mm#{i}" end + } + #for i in 0...n do puts "movq [ebx+#{8*i}],mm#{i}" end + puts "lea edi,[edi+#{8*n}]" + puts "lea esi,[esi+#{8*n}]" + #puts "lea ebx,[ebx+#{8*n}]" + puts "lea ecx,[ecx-#{8/size*n}]" + } + } + foo.call 4 + foo.call 1 + a.make_until("test ecx,ecx", "jz") { + # requires commutativity ??? fails with shl, shr + puts "mov #{accum},[esi]" + puts "#{opcode} #{$asm_type[type]} [edi],#{accum}" + #puts "mov #{accum},[edi]" + #puts "#{opcode} #{accum},[esi]" + #puts "mov [ebx],#{accum}" + puts "lea edi,[edi+#{size}]" + puts "lea esi,[esi+#{size}]" + #puts "lea ebx,[ebx+#{size}]" + puts "dec ecx" + } + puts "emms" + } + #$decls << "void #{s}(int,#{type}*,#{type}*,#{type}*);\n" + $decls << "void #{s}(int,#{type}*,#{type}*);\n" + $install << "FIX2PTR(Numop,rb_hash_aref(op_dict,SYM(#{sym})))" + $install << "->on_#{type}.op_zip = #{s};\n" + $count += 1 +end + +for op in $opcodes.keys do + for type in [:uint8, :int16#, :int32 + ] do + make_fun_map(op,type) + make_fun_zip(op,type) + end +end + +$loader.puts $decls +$loader.puts %` +}; /* extern */ +#include <stdlib.h> +void startup_mmx_loader () {/*bogus*/} +void startup_mmx () { + if (getenv("NO_MMX")) return; + if (EVAL(\"GridFlow.bridge_name\")!=Qnil) gfpost(\"startup_cpu: using MMX optimisations\"); + #{$install} +}` + +STDERR.puts "automatically generated #{$count} MMX asm functions" + +=begin notes: +CPUID has a bit for detecting MMX +PACKSSDW PACKSSWB PACKUSWB = saturation-casting +PCMPxx: Compare Packed Integers +PMULHW, PMULLW: Multiply Packed _unsigned_ 16-bit Integers, and Store +PUNPCKxxx: Unpack and Interleave Data +=end diff --git a/externals/gridflow/devices4ruby/ParallelPort.rb b/externals/gridflow/devices4ruby/ParallelPort.rb new file mode 100644 index 00000000..10188592 --- /dev/null +++ b/externals/gridflow/devices4ruby/ParallelPort.rb @@ -0,0 +1,72 @@ +require "linux/ioctl" +# Copyright (c) 2001, 2003 by Mathieu Bouchard +# this is published under the Ruby license + +=begin + if using a DB-25 female connector as found on a PC, + then the pin numbering is like: + 13 _____ 1 + 25 \___/ 14 + + 1 = STROBE = the clock line is a square wave, often at 9600 Hz, + which determines the data rate in usual circumstances. + 2..9 = D0..D7 = the eight ordinary data bits + 10 = -ACK (status bit 6 ?) + 11 = BUSY (status bit 7) + 12 = PAPER_END (status bit 5) + 13 = SELECT (status bit 4 ?) + 14 = -AUTOFD + 15 = -ERROR (status bit 3 ?) + 16 = -INIT + 17 = -SELECT_IN + 18..25 = GROUND +=end + +module Linux; module ParallelPort + extend IoctlClass + + @port_flags = %w[ + LP_EXIST + LP_SELEC + LP_BUSY + LP_OFFL + LP_NOPA + LP_ERR + LP_ABORT + LP_CAREFUL + LP_ABORTOPEN + LP_TRUST_IRQ + ] + + @port_status = %w[ + nil, + nil, + nil, + LP_PERRORP # unchanged input, active low + LP_PSELECD # unchanged input, active high + LP_POUTPA # unchanged input, active high + LP_PACK # unchanged input, active low + LP_PBUSY # inverted input, active high + ] + + LPCHAR = 0x0601 + LPTIME = 0x0602 + LPABORT = 0x0604 + LPSETIRQ = 0x0605 + LPGETIRQ = 0x0606 + LPWAIT = 0x0608 + LPCAREFUL = 0x0609 # obsoleted??? wtf? + LPABORTOPEN = 0x060a + LPGETSTATUS = 0x060b # return LP_S(minor) + LPRESET = 0x060c # reset printer + LPGETSTATS = 0x060d # struct lp_stats (most likely turned off) + LPGETFLAGS = 0x060e # get status flags + LPTRUSTIRQ = 0x060f # set/unset the LP_TRUST_IRQ flag + + ioctl_reader :port_flags , :LPGETFLAGS + ioctl_reader :port_status, :LPGETSTATUS + ioctl_writer :port_careful,:LPCAREFUL + ioctl_writer :port_char, :LPCHAR + +end end + diff --git a/externals/gridflow/devices4ruby/SoundMixer.rb b/externals/gridflow/devices4ruby/SoundMixer.rb new file mode 100644 index 00000000..0ae50b60 --- /dev/null +++ b/externals/gridflow/devices4ruby/SoundMixer.rb @@ -0,0 +1,152 @@ +require "linux/ioctl" + +module Linux; module SoundMixer + extend IoctlClass + + MIXER_NRDEVICES = 0x00000019 + MIXER_VOLUME = 0x00000000 + MIXER_BASS = 0x00000001 + MIXER_TREBLE = 0x00000002 + MIXER_SYNTH = 0x00000003 + MIXER_PCM = 0x00000004 + MIXER_SPEAKER = 0x00000005 + MIXER_LINE = 0x00000006 + MIXER_MIC = 0x00000007 + MIXER_CD = 0x00000008 + MIXER_IMIX = 0x00000009 + MIXER_ALTPCM = 0x0000000a + MIXER_RECLEV = 0x0000000b + MIXER_IGAIN = 0x0000000c + MIXER_OGAIN = 0x0000000d + MIXER_LINE1 = 0x0000000e + MIXER_LINE2 = 0x0000000f + MIXER_LINE3 = 0x00000010 + MIXER_DIGITAL1 = 0x00000011 + MIXER_DIGITAL2 = 0x00000012 + MIXER_DIGITAL3 = 0x00000013 + MIXER_PHONEIN = 0x00000014 + MIXER_PHONEOUT = 0x00000015 + MIXER_VIDEO = 0x00000016 + MIXER_RADIO = 0x00000017 + MIXER_MONITOR = 0x00000018 + ONOFF_MIN = 0x0000001c + ONOFF_MAX = 0x0000001e + MIXER_NONE = 0x0000001f + MIXER_ENHANCE = 0x0000001f + MIXER_MUTE = 0x0000001f + MIXER_LOUD = 0x0000001f + MIXER_RECSRC = 0x000000ff + MIXER_DEVMASK = 0x000000fe + MIXER_RECMASK = 0x000000fd + MIXER_CAPS = 0x000000fc + MIXER_STEREODEVS = 0x000000fb + MIXER_OUTSRC = 0x000000fa + MIXER_OUTMASK = 0x000000f9 + MASK_VOLUME = 0x00000001 + MASK_BASS = 0x00000002 + MASK_TREBLE = 0x00000004 + MASK_SYNTH = 0x00000008 + MASK_PCM = 0x00000010 + MASK_SPEAKER = 0x00000020 + MASK_LINE = 0x00000040 + MASK_MIC = 0x00000080 + MASK_CD = 0x00000100 + MASK_IMIX = 0x00000200 + MASK_ALTPCM = 0x00000400 + MASK_RECLEV = 0x00000800 + MASK_IGAIN = 0x00001000 + MASK_OGAIN = 0x00002000 + MASK_LINE1 = 0x00004000 + MASK_LINE2 = 0x00008000 + MASK_LINE3 = 0x00010000 + MASK_DIGITAL1 = 0x00020000 + MASK_DIGITAL2 = 0x00040000 + MASK_DIGITAL3 = 0x00080000 + MASK_PHONEIN = 0x00100000 + MASK_PHONEOUT = 0x00200000 + MASK_RADIO = 0x00800000 + MASK_VIDEO = 0x00400000 + MASK_MONITOR = 0x01000000 + MASK_MUTE = 0x80000000 + MASK_ENHANCE = 0x80000000 + MASK_LOUD = 0x80000000 + MIXER_READ_VOLUME = 0x80044d00 + MIXER_READ_BASS = 0x80044d01 + MIXER_READ_TREBLE = 0x80044d02 + MIXER_READ_SYNTH = 0x80044d03 + MIXER_READ_PCM = 0x80044d04 + MIXER_READ_SPEAKER = 0x80044d05 + MIXER_READ_LINE = 0x80044d06 + MIXER_READ_MIC = 0x80044d07 + MIXER_READ_CD = 0x80044d08 + MIXER_READ_IMIX = 0x80044d09 + MIXER_READ_ALTPCM = 0x80044d0a + MIXER_READ_RECLEV = 0x80044d0b + MIXER_READ_IGAIN = 0x80044d0c + MIXER_READ_OGAIN = 0x80044d0d + MIXER_READ_LINE1 = 0x80044d0e + MIXER_READ_LINE2 = 0x80044d0f + MIXER_READ_LINE3 = 0x80044d10 + MIXER_READ_MUTE = 0x80044d1f + MIXER_READ_ENHANCE = 0x80044d1f + MIXER_READ_LOUD = 0x80044d1f + MIXER_READ_RECSRC = 0x80044dff + MIXER_READ_DEVMASK = 0x80044dfe + MIXER_READ_RECMASK = 0x80044dfd + MIXER_READ_STEREODEVS = 0x80044dfb + MIXER_READ_CAPS = 0x80044dfc + MIXER_WRITE_VOLUME = 0xc0044d00 + MIXER_WRITE_BASS = 0xc0044d01 + MIXER_WRITE_TREBLE = 0xc0044d02 + MIXER_WRITE_SYNTH = 0xc0044d03 + MIXER_WRITE_PCM = 0xc0044d04 + MIXER_WRITE_SPEAKER = 0xc0044d05 + MIXER_WRITE_LINE = 0xc0044d06 + MIXER_WRITE_MIC = 0xc0044d07 + MIXER_WRITE_CD = 0xc0044d08 + MIXER_WRITE_IMIX = 0xc0044d09 + MIXER_WRITE_ALTPCM = 0xc0044d0a + MIXER_WRITE_RECLEV = 0xc0044d0b + MIXER_WRITE_IGAIN = 0xc0044d0c + MIXER_WRITE_OGAIN = 0xc0044d0d + MIXER_WRITE_LINE1 = 0xc0044d0e + MIXER_WRITE_LINE2 = 0xc0044d0f + MIXER_WRITE_LINE3 = 0xc0044d10 + MIXER_WRITE_MUTE = 0xc0044d1f + MIXER_WRITE_ENHANCE = 0xc0044d1f + MIXER_WRITE_LOUD = 0xc0044d1f + MIXER_WRITE_RECSRC = 0xc0044dff + MIXER_INFO = 0x805c4d65 + MIXER_ACCESS = 0xc0804d66 + MIXER_AGC = 0xc0044d67 + MIXER_3DSE = 0xc0044d68 + MIXER_PRIVATE1 = 0xc0044d6f + MIXER_PRIVATE2 = 0xc0044d70 + MIXER_PRIVATE3 = 0xc0044d71 + MIXER_PRIVATE4 = 0xc0044d72 + MIXER_PRIVATE5 = 0xc0044d73 + MIXER_GETLEVELS = 0xc0a44d74 + MIXER_SETLEVELS = 0xc0a44d75 + + DEVICE_LABELS = [ + "Vol ", "Bass ", "Trebl", "Synth", "Pcm ", + "Spkr ","Line ", "Mic ", "CD ", "Mix ", + "Pcm2 ","Rec ", "IGain", "OGain", + "Line1", "Line2", "Line3", "Digital1", "Digital2", "Digital3", + "PhoneIn", "PhoneOut", "Video", "Radio", "Monitor" + ] + + DEVICE_NAMES = [ + "vol", "bass", "treble", "synth", "pcm", "speaker", "line", + "mic", "cd", "mix", "pcm2", "rec", "igain", "ogain", + "line1", "line2", "line3", "dig1", "dig2", "dig3", + "phin", "phout", "video", "radio", "monitor" + ] + + DEVICE_NAMES.each_with_index {|name,i| + ioctl_accessor name, + MIXER_READ_VOLUME+i, + MIXER_WRITE_VOLUME+i + } + +end end diff --git a/externals/gridflow/devices4ruby/SoundPCM.rb b/externals/gridflow/devices4ruby/SoundPCM.rb new file mode 100644 index 00000000..d1b97159 --- /dev/null +++ b/externals/gridflow/devices4ruby/SoundPCM.rb @@ -0,0 +1,102 @@ +# $Id: SoundPCM.rb,v 1.1 2005-10-04 02:02:14 matju Exp $ +require "linux/ioctl" + +module Linux + +module SoundPCM + extend IoctlClass + + # SNDCTL Kernel Procedure Numbers + + SEQ_RESET = 0x00005100 + SEQ_SYNC = 0x00005101 + SEQ_CTRLRATE = 0xc0045103 + SEQ_GETOUTCOUNT = 0x80045104 + SEQ_GETINCOUNT = 0x80045105 + SEQ_PERCMODE = 0x40045106 + SEQ_TESTMIDI = 0x40045108 + SEQ_RESETSAMPLES = 0x40045109 + SEQ_NRSYNTHS = 0x8004510a + SEQ_NRMIDIS = 0x8004510b + SEQ_THRESHOLD = 0x4004510d + SEQ_PANIC = 0x00005111 + SEQ_OUTOFBAND = 0x40085112 + SEQ_GETTIME = 0x80045113 + + SYNTH_INFO = 0xc08c5102 + SYNTH_MEMAVL = 0xc004510e + SYNTH_ID = 0xc08c5114 + SYNTH_CONTROL = 0xcfa45115 + SYNTH_REMOVESAMPLE = 0xc00c5116 + + FM_LOAD_INSTR = 0x40285107 + FM_4OP_ENABLE = 0x4004510f + + TMR_TIMEBASE = 0xc0045401 + TMR_START = 0x00005402 + TMR_STOP = 0x00005403 + TMR_CONTINUE = 0x00005404 + TMR_TEMPO = 0xc0045405 + TMR_SOURCE = 0xc0045406 + TMR_METRONOME = 0x40045407 + TMR_SELECT = 0x40045408 + + MIDI_INFO = 0xc074510c + MIDI_PRETIME = 0xc0046d00 + MIDI_MPUMODE = 0xc0046d01 + MIDI_MPUCMD = 0xc0216d02 + + # DSP_* names are obsolete ? + DSP_STEREO = 0xc0045003 + DSP_GETBLKSIZE = 0xc0045004 + DSP_SETDUPLEX = 0x00005016 + DSP_GETODELAY = 0x80045017 + DSP_PROFILE = 0x40045017 + + # what is this? + COPR_RESET = 0x00004300 + COPR_LOAD = 0xcfb04301 + COPR_RDATA = 0xc0144302 + COPR_RCODE = 0xc0144303 + COPR_WDATA = 0x40144304 + COPR_WCODE = 0x40144305 + COPR_RUN = 0xc0144306 + COPR_HALT = 0xc0144307 + COPR_SENDMSG = 0xcfa44308 + COPR_RCVMSG = 0x8fa44309 + + # SOUND_PCM Kernel Procedure Numbers + + PCM_READ_BITS , PCM_WRITE_BITS = 0x80045005, 0xc0045005 + PCM_READ_CHANNELS , PCM_WRITE_CHANNELS = 0x80045006, 0xc0045006 + PCM_READ_FILTER , PCM_WRITE_FILTER = 0x80045007, 0xc0045007 + PCM_READ_RATE , PCM_WRITE_RATE = 0x80045002, 0xc0045002 + + PCM_RESET = 0x00005000 + PCM_SYNC = 0x00005001 + PCM_POST = 0x00005008 + PCM_SUBDIVIDE = 0xc0045009 + PCM_SETFRAGMENT = 0xc004500a + PCM_GETFMTS = 0x8004500b + PCM_SETFMT = 0xc0045005 + PCM_GETOSPACE = 0x8010500c + PCM_GETISPACE = 0x8010500d + PCM_NONBLOCK = 0x0000500e + PCM_GETCAPS = 0x8004500f + PCM_GETTRIGGER , PCM_SETTRIGGER = 0x80045010, 0x40045010 + PCM_SETSYNCRO = 0x00005015 + PCM_GETIPTR = 0x800c5011 + PCM_GETOPTR = 0x800c5012 + PCM_MAPINBUF = 0x80085013 + PCM_MAPOUTBUF = 0x80085014 + + ioctl_accessor :bits , :PCM_READ_BITS , :PCM_WRITE_BITS + ioctl_accessor :channels, :PCM_READ_CHANNELS, :PCM_WRITE_CHANNELS + ioctl_accessor :filter , :PCM_READ_FILTER , :PCM_WRITE_FILTER + ioctl_accessor :rate , :PCM_READ_RATE , :PCM_WRITE_RATE +end + +# backward compatibility +SoundDSP = SoundPCM + +end # Linux diff --git a/externals/gridflow/devices4ruby/extconf.rb b/externals/gridflow/devices4ruby/extconf.rb new file mode 100644 index 00000000..9e49cbdc --- /dev/null +++ b/externals/gridflow/devices4ruby/extconf.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby +# $Id: extconf.rb,v 1.1 2005-10-04 02:02:14 matju Exp $ +# installer for RubyX11 / MetaRuby / etc +# by Mathieu Bouchard + +require "rbconfig" +require "ftools" +include Config + +$DESTDIR = "#{CONFIG["sitedir"]}/#{CONFIG["MAJOR"]}.#{CONFIG["MINOR"]}" +#$DESTDIR = "/home/matju/lib/ruby/#{RUBY_VERSION[0,3]}" +$RUBY = "ruby" + +while ARGV.length>0 + arg=ARGV.shift + case arg + when /=/ + i=arg.index '=' + ARGV.unshift arg[0..i-1], arg[i+1..-1] + when "--prefix" + $DESTDIR = ARGV.shift + "/lib/ruby/#{CONFIG["MAJOR"]}.#{CONFIG["MINOR"]}" + end +end + + + +def install_files(f,base,entries) + entries.each {|type,name,*rest| + case type + when :ruby + f.puts "\tinstall -m644 #{base+name} $(DESTDIR)/#{base+name}" + when :directory + f.puts "\t@mkdir $(DESTDIR)/#{base+name} || true" + install_files(f,base+name,rest) + end + } +end + +def uninstall_files(f,base,entries) + entries.each {|type,name,*rest| + case type + when :ruby + f.puts "\trm $(DESTDIR)/#{base+name}" + when :directory + uninstall_files(f,base+name,rest) + end + } +end + +def make_makefile + File.open("Makefile","w") {|f| + f.puts "# Warning: this file is GENERATED by ./extconf.rb", "" + f.puts "DESTDIR = #{$DESTDIR}", "" + f.puts "RUBY = #{$RUBY}" + f.puts "all::", "" + f.puts "Makefile: extconf.rb" + f.puts "\t$(RUBY) extconf.rb", "" + + f.puts "install::" + f.puts "\t@mkdir -p $(DESTDIR)" + install_files(f,"",FILES) + f.puts + f.puts "uninstall::" + uninstall_files(f,"",FILES) + f.puts + } + #FILES.each {|name| + # File.install "lib/#{name}", "#{DSTPATH}/#{name}", 0644, true + #end +end + +#----------------------------------------------------------------# + +$DESTDIR += "/linux/" #(HACK!) + +FILES = [ +# [:directory, "linux/", + [:ruby, "ioctl.rb"], + [:ruby, "SoundPCM.rb"], + [:ruby, "ParallelPort.rb"], + [:ruby, "SoundMixer.rb"], +# ] +] + +make_makefile + + +__END__ +### the following is discarded (just a test) + +require "mkmf" + +srcs = %w( + termios +) + +#have_library("m") +#have_func("sincos") +#have_func("asinh") + +#if have_header("fftw.h") +# if have_library("fftw", "fftwnd_create_plan") +# srcs.push "na_fftw" +# else +# $defs.delete "-DHAVE_FFTW_H" +# end +#end + +$objs = srcs.map {|i| i+".o"} + +#dir_config("linux") +create_makefile("linux") diff --git a/externals/gridflow/devices4ruby/ioctl.rb b/externals/gridflow/devices4ruby/ioctl.rb new file mode 100644 index 00000000..100d138d --- /dev/null +++ b/externals/gridflow/devices4ruby/ioctl.rb @@ -0,0 +1,66 @@ +# general-purpose code for performing +# less-than-trivial IOCTL operations. +# note that this is quite hackish +# but is still better than writing actual C code. + +module Linux; DEVICES_VERSION = "0.1.1"; end + +module IoctlClass + def ioctl_reader(sym,cmd_in) + module_eval %{def #{sym} + ioctl_intp_in(#{cmd_in}) + end} + end + def ioctl_writer(sym,cmd_out) + module_eval %{def #{sym}=(v) + ioctl_intp_out(#{cmd_out},v) + #{sym} if respond_to? :#{sym} + end} + end + def ioctl_accessor(sym,cmd_in,cmd_out) + ioctl_reader(sym,cmd_in) + ioctl_writer(sym,cmd_out) + end +end + +module Ioctl + # this method is not used anymore + def int_from_4(foo) + # if it crashes, just insert foo=foo.reverse here. + (foo[0]+0x100*foo[1])+0x10000*(foo[2]+0x100*foo[3]) + end + +# this was a big hack (from hell) that i used until I actually +# learned the other feature of ioctl(). +=begin + def ioctl_intp_out(arg1,arg2) + tmp = arg2 + 2**32 + foo = [2*tmp.id + 16].pack("l").unpack("P4")[0] + tmp_ptr = int_from_4(foo) +# STDOUT.printf "tmp_ptr=%x\n", tmp_ptr + ioctl(arg1,tmp_ptr) + end + + def ioctl_intp_in(arg1) + tmp = 0xdeadbeef + 2**32 + foo = [2*tmp.id + 16].pack("l").unpack("P4")[0] + tmp_ptr = int_from_4(foo) +# tmp_ptr = foo.unpack("l")[0] +# STDOUT.printf "tmp_ptr=%x\n", tmp_ptr + ioctl(arg1,tmp_ptr) + tmp & (2**32-1) + end +=end + + def ioctl_intp_out(arg1,arg2) + ioctl(arg1,[arg2].pack("l")) + end + + def ioctl_intp_in(arg1) + ioctl(arg1,s="blah") + return s.unpack("l")[0] + end + +end + +class IO; include Ioctl; end diff --git a/externals/gridflow/doc/moulinette.rb b/externals/gridflow/doc/moulinette.rb new file mode 100644 index 00000000..03dbfa86 --- /dev/null +++ b/externals/gridflow/doc/moulinette.rb @@ -0,0 +1,612 @@ +=begin + $Id: moulinette.rb,v 1.1 2005-10-04 02:02:14 matju Exp $ + convert GridFlow Documentation XML to HTML with special formatting. + + 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 + +GF_VERSION = "0.8.0" + +#$use_rexml = true +$use_rexml = false + +#require "gridflow" + +if $use_rexml + # this is a pure ruby xml-parser + begin + require "rexml/sax2parser" + rescue LoadError + require "rexml/parsers/sax2parser" + include REXML::Parsers + end + include REXML +else + # this uses libexpat.so + require "xmlparser" +end + +=begin todo + + [ ] make it use the mk() function as much as possible. + [ ] make it validate + [ ] make it find the size of the pictures (and insert width/height attrs) + [ ] tune the output + [ ] fix the header of the page + +=end + +if nil + alias real_print print + alias real_puts puts + def print(*x); real_print "[#{caller[0]}]"; real_print *x; end + def puts (*x); real_print "[#{caller[0]}]"; real_puts *x; end +end + +def warn(text) + STDERR.print "\e[1;031mWARNING:\e[0m " + STDERR.puts text +end + +$escape_map={ + "<" => "<", + ">" => ">", + "&" => "&", +} + +# hackish transcoding from unicode to iso-8859-1 +def multicode(text); text.gsub(/\xc2(.)/) { $1 } end + +def html_quote(text) + return nil if not text + text = text.gsub(/[<>&]/) {|x| $escape_map[x] } + text = multicode(text) if /\xc2/ =~ text + text +end + +def mk(tag,*values,&block) + raise "value-list's length must be even" if values.length % 2 != 0 + print "<#{tag}" + i=0 + while i<values.length + print " #{values[i]}=\"#{values[i+1]}\"" + i+=2 + end + print ">" + (block[]; mke tag) if block +end +def mke(tag) + print "</#{tag}>" +end + +def mkimg(parent,alt=nil,prefix=nil) + #STDERR.puts parent.to_s + icon = parent.contents.find {|x| XNode===x and x.tag == 'icon' } + name = parent.att["name"] + url = prefix+"/"+name+"-icon.png" + if icon and icon.att["src"] + url = icon.att["src"] + STDERR.puts "overriding #{url} with #{icon.att["src"]}" + end + url = url.sub(/,.*$/,"") # what's this for again? + warn "icon #{url} not found" if not File.exist? url + url = url.gsub(%r"#") {|x| sprintf "%%%02x", x[0] } + alt = icon.att["text"] if icon and not alt + alt = "[#{name}]" + mk(:img, :src, url, :alt, alt, :border, 0) +end + +class XString < String + def show + print html_quote(gsub(/[\r\n\t ]+$/," ")) + end +end + +module HasOwnLayout; end + +class XNode + # subclass interface: + # #show_index : print as html in index + # #show : print as html in main part of the doc + + @valid_tags = {} + class<<self + attr_reader :valid_tags + def register(*args,&b) + qlass = (if b then Class.new self else self end) + qlass.class_eval(&b) if b + for k in args do XNode.valid_tags[k]=qlass end + #qlass.class_eval { + # public :show + # public :show_index + #} + end + end + + def initialize tag, att, *contents + @tag,@att,@contents = + tag, att, contents + contents.each {|c| c.parent = self if XNode===c } + end + + attr_reader :tag, :att, :contents + attr_accessor :parent + def [] i; contents[i] end + + def show_index + contents.each {|x| next unless XNode===x; x.show_index } + end + + # this method segfaults in ruby 1.8 + # because of method lookup on Qundef or whatever. + def show + #STDERR.puts GridFlow.get_id(contents) + #STDERR.puts self + contents.each {|x| + # STDERR.puts GridFlow.get_id(x) + x.show + } + end + def inspect; "#<XNode #{tag}>"; end + def to_s; inspect; end +end + +XNode.register("documentation") {} + +XNode.register(*%w( icon help arg rest )) {public + def show; end +} + +XNode.register("section") {public + def show + write_black_ruler + mk(:tr) { mk(:td,:colspan,4) { + mk(:a,:name,att["name"].gsub(/ /,'_')) {} + mk(:h4) { print att["name"] }}} + + contents.each {|x| + if HasOwnLayout===x then + x.show + else + mk(:tr) { mk(:td) {}; mk(:td) {}; mk(:td) { x.show }} + puts "" + end + } + + mk(:tr) { mk(:td) { print " " }} + puts "" + end + def show_index + mk(:h4) { + mk(:a,:href,"#"+att["name"].gsub(/ /,'_')) { + print att["name"] }} + print "<ul>\n" + super + print "</ul>\n" + end +} + +# basic text formatting nodes. +XNode.register(*%w( p i u b sup )) {public + def show + print "<#{tag}>" + super + print "</#{tag}>" + end +} + +XNode.register("k") {public + def show + print "<kbd><font color=\"#007777\">" # oughta be in stylesheet? + super + print "</font></kbd>" + end +} + +# explicit hyperlink on the web. +XNode.register("link") {public + def show + STDERR.puts "att = #{att.inspect}" + raise if not att['to'] + print "<a href='#{att['to']}'>" + super + print att[:to] if contents.length==0 + print "</a>" + end +} + +XNode.register("list") {public + attr_accessor :counter + def show + self.counter = att.fetch("start"){"1"}.to_i + mk(:ul) { + super # method call on Qundef ??? + } + end +} + +XNode.register("li") {public + def show + mk(:li) { + print "<b>#{parent.counter}</b>", " : " + parent.counter += 1 + super + } + end +} + +# and "macro", "enum", "type", "use" +XNode.register("class") {public + include HasOwnLayout + def show + tag = self.tag + name = att['name'] or raise + mk(:tr) { + mk(:td,:colspan,4,:bgcolor,"#ffb080") { + mk(:b) { print " "*2, "#{tag} " } + mk(:a,:name,name) { print name } + } + } + mk(:tr) { + mk(:td) {} + mk(:td,:valign,:top) { + print "<br>\n" + help = contents.find {|x| XNode===x and x.tag == 'help' } + mkimg(self,nil,"flow_classes") if /reference|format/ =~ $file + mk(:br,:clear,"left") + 2.times { mk(:br) } + if help + big = help.att['image'] || att['name'] + if big[0]==?@ then big="images/help_#{big}.png" end + warn "help #{big} not found" if not File.exist?(big) + #small = big.gsub(/png$/, 'jpg').gsub(/\//, '/ic_') + mk(:a,:href,big) { + #mk(:img,:src,small,:border,0) + mk(:img,:src,"images/see_screenshot.png",:border,0) + } + end + mk(:br,:clear,"left") + mk(:br) + }#/td + mk(:td) { + print "<br>\n" + super + print "<br>" + }#/td + }#/tr + end + def show_index + icon = contents.find {|x| XNode===x && x.tag == "icon" } + if not att["name"] then + raise "name tag missing?" + end + mk(:li) { mk(:a,:href,"\#"+att["name"]) { + mkimg(self,att["cname"],"flow_classes") + }} + puts + super + end +} + +def nice_table + mk(:table,:border,0,:bgcolor,:black,:cellspacing,1) { + mk(:tr) { + mk(:td,:valign,:top,:align,:left) { + mk(:table,:bgcolor,:white,:border,0, + :cellpadding,4,:cellspacing,1) { + yield }}}} +end + +XNode.register("attr") {public + def show + print "<br>" + if parent.tag == "inlet" or parent.tag == "outlet" + mk(:b) { + print "#{parent.tag} #{parent.att['id']} " + + } + end + print "<b>#{tag}</b> " + print "#{html_quote att['name']} <b>(</b>" + s=html_quote(att["name"]) + s="<i>#{att['type']}</i> #{s}" if att['type'] + print "<b>#{s}</b>" + print "<b>)</b> " + end +} + +XNode.register("method") {public +if true # + def show + print "<br>" + if parent.tag == "inlet" or parent.tag == "outlet" + mk(:b) { + print "#{parent.tag} #{parent.att['id']} " + + } + end + print "<b>#{tag}</b> " + print "#{html_quote att['name']} <b>(</b>" + print contents.map {|x| + next unless XNode===x + case x.tag + when "arg" + s=html_quote(x.att["name"]) + s="<i>#{x.att['type']}</i> #{s}" if x.att['type'] + s + when "rest" + (x.att["name"]||"") + "..." + end + }.compact.join("<b>, </b>") + print "<b>)</b> " + super + print "<br>\n" + end +else # + def show + print "<br>" + mk(:table) { mk(:tr) { mk(:td) { + name = "" + name << "#{parent.tag} #{parent.att['id']} " if \ + parent.tag == "inlet" or parent.tag == "outlet" + name << tag.to_s + mk(:b) { print name.gsub(/ /," ") } + }; mk(:td) { + nice_table { mk(:tr) { + mk(:td,:width,1) { print html_quote(att['name']) } + contents.each {|x| + next unless XNode===x + case x.tag + when "arg" + mk(:td,:bgcolor,:pink) { + s = "" + if x.att["type"] + s << "<i>" << html_quote(x.att["type"]) << "</i>" + end + if x.att["name"] + s << " " << html_quote(x.att["name"]) + end + s<<" " if s.length==0 + mk(:b) { puts s } + } + when "rest" + mk(:td,:bgcolor,:pink) { + mk(:b) { print html_quote(x.att["name"]), "..."} + } + end + } + }} + }; mk(:td) { + super + }}} + end +end # +} + +XNode.register("table") {public + def show + colors = ["#ffffff","#f0f8ff",] + rows = contents.find_all {|x| XNode===x && x.tag=="row" } + rows.each_with_index {|x,i| x.bgcolor = colors[i%2] } + mk(:tr) { + 2.times { mk(:td) {} } + mk(:td) { + nice_table { + mk(:tr) { + columns = contents.find_all {|x| XNode===x && x.tag=="column" } + columns.each {|x| mk(:td,:bgcolor,"#808080") { + mk(:font,:color,"#ffffff") { + mk(:b) { + x.contents.each {|y| y.show }}}}} + } + super + }}} + end +} + +XNode.register("column") {public + def show; end +} + +XNode.register("row") {public + attr_accessor :bgcolor + def show + columns = parent.contents.find_all {|x| XNode===x && x.tag=="column" } + mk(:tr) { columns.each {|x| mk(:td,:bgcolor,bgcolor) { + id = x.att["id"] + case x.att["type"] + when "icon" # should fix this for non-op icons + x = "op/#{att['cname']}-icon.png" + if not File.exist? x + warn "no icon for #{att['name']} (#{x})\n" + end + mk(:img,:src,x,:border,0,:alt,att["name"]) + else + if id=="" + then contents.each {|x| x.show } + else +# print html_quote(att[id] || "--") + print multicode(att[id] || "--") + end + end + }}} + end +} + +XNode.register("inlet","outlet") {} + +#----------------------------------------------------------------# + +if $use_rexml + class GFDocParser + def initialize(file) + @sax = SAX2Parser.new(File.open(file)) + @xml_lists = [] + @stack = [[]] + @sax.listen(:start_element) {|a,b,c,d| startElement(b,d) } + @sax.listen( :end_element) {|a,b,c| endElement(b) } + @sax.listen( :characters) {|a| @gfdoc.character(a) } + end + def do_it; @sax.parse; end + end +else + class GFDocParser + def initialize(file) + @xml = XMLParser.new("ISO-8859-1") + foo=self; @xml.instance_eval { @gfdoc=foo } + def @xml.startElement(tag,attrs) @gfdoc.startElement(tag,attrs) end + def @xml.endElement(tag) @gfdoc.endElement(tag) end + def @xml.character(text) @gfdoc.character(text) end + @file = File.open file + @xml_lists = [] + @stack = [[]] + end + def do_it; @xml.parse(@file.readlines.join("\n"), true) end + def method_missing(sel,*args) @xml.send(sel,*args) end + end +end + +class GFDocParser + attr_reader :stack + def startElement(tag,attrs) + if not XNode.valid_tags[tag] then + raise XMLParserError, "unknown tag #{tag}" + end + @stack<<[tag,attrs] + end + def endElement(tag) + node = XNode.valid_tags[tag].new(*@stack.pop) + @stack.last << node + end + def character(text) + if not String===@stack.last.last then + @stack.last << XString.new("") + end + @stack.last.last << text + end +end + +#----------------------------------------------------------------# + +def write_header(tree) +puts <<EOF +<html><head> +<!-- #{"$"}Id#{"$"} --> +<title>GridFlow #{GF_VERSION} - #{tree.att['title']}</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<link rel="stylesheet" href="gridflow.css" type="text/css"> +</head> +<body bgcolor="#FFFFFF" + leftmargin="0" topmargin="0" + marginwidth="0" marginheight="0"> +<table width="100%" bgcolor="white" border="0" cellspacing="2"> +<tr><td colspan="4" bgcolor="#082069"> +<img src="images/titre_gridflow.png" width="253" height="23"> +</td></tr><tr><td> </td></tr> +EOF +write_black_ruler +puts <<EOF +<tr><td colspan="4" height="16"> + <h4>GridFlow #{GF_VERSION} - #{tree.att['title']}</h4> +</td></tr> +<tr> + <td width="5%" rowspan="2"> </td> + <td width="15%" height="23"> </td> + <td width="80%" height="23"> </td> + <td width="5%" height="23"> </td> +</tr> +EOF +end + +def write_black_ruler +puts <<EOF +<tr><td colspan="4" bgcolor="black"> +<img src="images/black.png" width="1" height="2"></td></tr> +EOF +end + +def write_footer +puts <<EOF +<td colspan="4" bgcolor="black"> +<img src="images/black.png" width="1" height="2"></td></tr> +<tr><td colspan="4"> +<p><font size="-1"> +GridFlow #{GF_VERSION} Documentation<br> +Copyright © 2001,2002,2003,2004,2005 by Mathieu Bouchard +<a href="mailto:matju@sympatico.ca">matju@artengine.ca</a> +</font></p> +</td></tr></table></body></html> +EOF +end + +#----------------------------------------------------------------# + +$nodes = {} +XMLParserError = Exception if $use_rexml + +def read_one_page file + begin + STDERR.puts "reading #{file}" + parser = GFDocParser.new(file) + parser.do_it + $nodes[file] = parser.stack[0][0] + rescue Exception => e + puts "" + puts "" + STDERR.puts e.inspect + i = parser.stack.length-1 + (STDERR.puts "\tinside <#{parser.stack[i][0]}>"; i-=1) until i<1 + # strange that line numbers are doubled. + # also the byte count is offset by the line count !?!?!? + STDERR.puts "\tinside #{file}:#{parser.line/2 + 1}" + + " (column #{parser.column}," + + " byte #{parser.byteIndex - parser.line/2})" + raise "why don't you fix the documentation" + end +end + +def write_one_page file + begin + $file = file + output_name = file.sub(/\.xml/,".html") + STDERR.puts "writing #{output_name}" + STDOUT.reopen output_name, "w" + tree = $nodes[file] + write_header(tree) + mk(:tr) { mk(:td,:colspan,2) { mk(:div,:cols,tree.att["indexcols"]||1) { + tree.show_index + puts "<br><br>" + }}} + tree.show + write_footer + puts "" + puts "" + rescue Exception => e + STDERR.puts "#{e.class}: #{e.message}" + STDERR.puts e.backtrace + end +end + +$files = %w( + install.xml project_policy.xml + reference.xml format.xml internals.xml architecture.xml) + +$files.each {|input_name| read_one_page input_name } +$files.each {|input_name| write_one_page input_name } diff --git a/externals/gridflow/extra/jmax_format.rb b/externals/gridflow/extra/jmax_format.rb new file mode 100644 index 00000000..c1b93ce5 --- /dev/null +++ b/externals/gridflow/extra/jmax_format.rb @@ -0,0 +1,167 @@ +=begin + $Id: jmax_format.rb,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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 + + +class JMaxFileHandler + Size = [4,1,2,4] + Packer = ["N","c","n","N"] + OpTable = [ + [0, :return], + [1, :push, :int], + [2, :push, :float], + [3, :push, :symbol], + [5, :set, :int], + [6, :set, :float], + [7, :set, :symbol], + [9, :pop_args, :int], + [10, :push_obj, :int], + [11, :mv_obj, :int], + [12, :pop_objs, :int], + [13, :make_obj, :int], + [14, :put_prop, :symbol], + [16, :obj_mess, :int, :symbol, :int], + [18, :push_obj_table, :int], + [19, :pop_obj_table], + [20, :connect], + [21, :make_top_obj, :int], + ] + OpTableById = {} + OpTableByName = {} + OpTable.each {|entry| + id,name,arg = entry + OpTableById[id] = entry + OpTableByName[name] = entry + } +end + +class JObject + attr_reader :properties + attr_accessor :parent_patcher + attr_reader :init_messages + attr_reader :connections + def self.[](*args) new(*args) end + def initialize(*args) + @args = args + @properties = {} + @init_messages = [] + end + def send_in(inlet,*args) + @init_messages << [inlet,*args] + end + def connect(inlet,target,outlet) + end + def subobjects + @subobjects={} if not defined? @subobjects + @subobjects + end +end + +class JMaxFileReader < JMaxFileHandler + def initialize(f,factory=JObject) + @f = f + @symbols = [] + @estack = [] + @ostack = [] + @tstack = [] + @factory = factory + end + def parse + magic, code_size, n_symbols = @f.read(12).unpack("a4NN") + case magic + when "bMax"; #ok + when "bMa2"; raise "bMa2 format (jMax 4) is not supported yet" + else raise "not a jMax file" + end + @code = @f.read code_size + @symbols = @f.read.split(/\0/).map {|x| x.intern } + @index = 0 + while @index < @code.size + read_opcode + end + raise "@estack.size!=0" if @estack.size!=0 + raise "@ostack.size!=1" if @ostack.size!=1 + raise "@tstack.size!=0" if @tstack.size!=0 + @ostack[0] + end + def read_opcode + #puts "#{@index} of #{@code.size}" + op = @code[@index]; @index+=1 + op1,op2 = op&0x3f,op>>6 + entry = OpTableById[op1] + if not entry + puts "skipping unknown opcode #{op1},#{op2}" + return + end + args = [] + (entry.length-2).times {|i| + args << (case entry[2+i] + when :int + n=Size[op2]; v=@code[@index,n].unpack(Packer[op2])[0] + x = if v[8*n-1]!=0 then ~(~v&((1<<(8*n-1))-1)) else v end + #STDERR.puts "WARNING: #{v} -> #{x}" if x<0 + x + when :float + n=4; @code[@index,4].unpack("g")[0] + when :symbol + n=Size[op2]; @symbols[@code[@index,n].unpack(Packer[op2])[0]] + when nil + end) + @index+=n + } + #text = sprintf "%05d: %2d,%1d: %s", @index, op1, op2, entry[1] + #text << "(" << args.map{|x|x.inspect}.join(",") << ")" + #puts text + send entry[1], *args + end + def push x; @estack << x end + def set x + if @estack.size>0 then @estack[-1]=x else @estack << x end + end + def put_prop x; @ostack[-1].properties[x] = @estack[-1] end + def make_obj x + patcher = @ostack[-1] if @ostack.size>0 + baby = @factory[*(@estack[-x,x].reverse)] + @ostack << baby + @ostack[-1].parent_patcher = patcher + patcher.subobjects[baby]=true if patcher + end + alias :make_top_obj :make_obj + def pop_args x; @estack[-x,x]=[] end + def push_obj_table x; @tstack<<[] end + def mv_obj x; @tstack[-1][x]=@ostack[-1] end + def pop_objs x; @ostack[-x,x]=[] end + def obj_mess i,s,n + o = @ostack[-1] + m = @estack[-n,n].reverse + if i<0 then o.send s,*m else o.send_in i,s,*m end + end + def push_obj x; @ostack<<@tstack[-1][x] end + def connect; @ostack[-1].connect @estack[-1],@ostack[-2],@estack[-2] end + def pop_obj_table; @tstack.pop end + def return; end +end + +if $0 == __FILE__ + jff = JMaxFileReader.new File.open("samples/fire.jmax") + jff.parse +end diff --git a/externals/gridflow/extra/puredata_format.rb b/externals/gridflow/extra/puredata_format.rb new file mode 100644 index 00000000..508f545e --- /dev/null +++ b/externals/gridflow/extra/puredata_format.rb @@ -0,0 +1,129 @@ +=begin + $Id: puredata_format.rb,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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 + +class PureDataFileWriter + def initialize filename + @f = File.open filename, "w" + end + def close; @f.close end + def write_patcher o + pr = o.properties + @f.puts "#N canvas #{pr[:wx]} #{pr[:wy]} #{pr[:ww]} #{pr[:wh]} 10;" + ol = o.subobjects.keys + x=0 + ol.find_all {|a| a.classname=="inlet"}.sort {|a,b| a.argv[0] <=> b.argv[0] }.each {|a| + if x>a.properties[:x] + a.properties[:x]=x+16 + STDERR.puts "warning: moving inlet #{a.argv[0]} to the right" + end + x=a.properties[:x] + } + x=0 + ol.find_all {|a| a.classname=="outlet"}.sort {|a,b| a.argv[0] <=> b.argv[0] }.each {|a| + if x>a.properties[:x] + a.properties[:x]=x+16 + STDERR.puts "warning: moving outlet #{a.argv[0]} to the right" + end + x=a.properties[:x] + } + ol.each {|so| write_object so } + ol.each_with_index {|so,i| + next if not so.instance_eval{defined? @outlets} + so.outlets.each_with_index {|conns,outlet| + next if not conns + conns.each {|target,inlet| + @f.puts "#X connect #{i} #{outlet} #{ol.index target} #{inlet};" + } + } + } + end + + def list_to_s l + l.map {|x| + if Array===x then "( " + list_to_s(x) + " )" else escape(x.to_s) end + }.join " " + end + + # what am i supposed to do? + def escape x; x.gsub(/[;,]/) {|x| " \\#{x}" }.gsub(/\n/) {"\\\n"} end + def escape2 x; x.gsub(/[,]/) {|x| " \\#{x} " }.gsub(/[;\$\"]/) {|x| "\\#{x}" }.gsub(/\n/) {"\\\n"} end + + def write_object o + pr = o.properties + #classname = o.class.instance_eval{@foreign_name} + classname = o.classname + if classname=="jpatcher" + #@f.print "#N canvas 0 0 " + write_patcher o + end + + case classname + when "display"; classname="print" + when "list"; classname="listmake" + end + + t = case classname + when "jcomment"; "text" + when "messbox"; "msg" + when "jpatcher"; "restore" + when "intbox"; "floatatom" + else "obj" + end + @f.print "#X #{t} #{pr[:x]} #{pr[:y]} " + + case classname + when "button" + @f.print "bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 -1" + when "jcomment" + @f.print escape2(pr[:comment].to_s) + when "messbox" + av=o.argv[0] + i=0 + dollar="$".intern + while i<av.length + if av[i]==dollar then av[i,2]=("\\$"+av[i+1].to_s).intern else i+=1 end + end + @f.print(list_to_s(av)) + when "slider" + #doradio = pr[:maxValue]-pr[:minValue]<=10 + doradio = false + #p doradio + name = case pr[:orientation] + when 1; if doradio then "hradio" else "hsl" end + when 2,nil; if doradio then "vradio" else "vsl" end + else raise "bogus slider orientation?" end + @f.print "#{name} "+ + "#{pr[:w]} #{pr[:h]} #{pr[:minValue]} #{pr[:maxValue]} 0 0 "+ + "empty empty empty -2 -6 0 8 -262144 -1 -1 0 1" + when "intbox" + @f.print "5 0 0 0 - - -;" + when "inlet"; @f.print "inlet" + when "outlet"; @f.print "outlet" + when "jpatcher" + @f.print("pd ",list_to_s(o.argv)) + else + @f.print(classname," ",list_to_s(o.argv)) + end + @f.puts ";" + end +end diff --git a/externals/gridflow/extra/server_1_grid.rb b/externals/gridflow/extra/server_1_grid.rb new file mode 100644 index 00000000..f1f4a9fc --- /dev/null +++ b/externals/gridflow/extra/server_1_grid.rb @@ -0,0 +1,26 @@ +# $Id: server_1_grid.rb,v 1.1 2005-10-04 02:02:15 matju Exp $ + +require "socket" +require "smpte" # in this folder + +picture = "\x7fGRID \000\003" +picture << [240,320,3].pack("N*") +make_smpte(picture) {|*rgb| rgb.pack "N*" } + +# File.open("blah.grid","w") {|f| f.write picture } + +serv = TCPServer.new 4242 +loop { + puts "waiting for connection (port 4242)" + sock = serv.accept + puts "incoming connection" + begin + loop { + sock.write picture + puts "wrote one picture" + } + rescue Errno::EPIPE # Broken Pipe + puts "connection closed (by client)" + # it's ok, go back to waiting. + end +} diff --git a/externals/gridflow/extra/server_1_ppm.rb b/externals/gridflow/extra/server_1_ppm.rb new file mode 100644 index 00000000..dad6d3f7 --- /dev/null +++ b/externals/gridflow/extra/server_1_ppm.rb @@ -0,0 +1,20 @@ +# $Id: server_1_ppm.rb,v 1.1 2005-10-04 02:02:15 matju Exp $ + +require "socket" + +picture = File.open("../images/teapot.ppm") {|x| x.read } + +serv = TCPServer.new 4242 +loop { + puts "waiting for connection (port 4242)" + sock = serv.accept + begin + loop { + sock.write picture + puts "wrote one picture" + } + rescue Errno::EPIPE # Broken Pipe + puts "connection closed (by client)" + # it's ok, go back to waiting. + end +} diff --git a/externals/gridflow/extra/server_2.rb b/externals/gridflow/extra/server_2.rb new file mode 100644 index 00000000..4807d502 --- /dev/null +++ b/externals/gridflow/extra/server_2.rb @@ -0,0 +1,65 @@ +# a server program to connect 2 or more clients together. +# by Mathieu Bouchard + +require "fcntl" +require "socket" + +class IO + def nonblock=flag + bit = Fcntl::O_NONBLOCK + fcntl(Fcntl::F_SETFL, (fcntl(Fcntl::F_GETFL) & ~bit) | + if flag then bit else 0 end) + end + # does not work with any ruby version, due to a bug. see below. + def read_at_most n + s="" + k=1<<(Math.log(n)/Math.log(2)).to_i + while k>0 + unless k+s.length>n + puts "trying #{k}" + (s << read(k)) rescue Errno::EWOULDBLOCK + end + k>>=1 + end + s + end + # this one works but is slow. + def bugfree_read_at_most n + s="" + (s << (read 1) while s.length<n) rescue Errno::EWOULDBLOCK + s + end +end + +serv = TCPServer.new 4242 +socks = [serv] + +loop { + puts "waiting for connection (port 4242)" + begin + loop { + puts "waiting" + ready,blah,crap = IO.select socks, [], socks, 1 + (ready||[]).each {|s| + if s==serv then + sock = serv.accept + sock.nonblock=true + socks << sock + puts "incoming connection (total: #{socks.length-1})" + else + other = socks.find_all{|x|not TCPServer===x} - [s] + stuff = s.bugfree_read_at_most 1024 + p stuff + (s.close; socks.delete s) if not stuff or stuff.length==0 + other.each {|x| + p x + x.write stuff + } + end + } + } + rescue Errno::EPIPE # Broken Pipe + puts "connection closed (by client)" + # it's ok, go back to waiting. + end +} diff --git a/externals/gridflow/extra/smpte.rb b/externals/gridflow/extra/smpte.rb new file mode 100644 index 00000000..c14c936e --- /dev/null +++ b/externals/gridflow/extra/smpte.rb @@ -0,0 +1,23 @@ +# Copyright 2001 by Mathieu Bouchard +# $Id: smpte.rb,v 1.1 2005-10-04 02:02:15 matju Exp $ + +# The standard SMPTE color test pattern. +# AS SEEN ON TV !!! (but this is a cheap plastic imitation) + +def make_smpte(picture="") + row_1 = "" + row_2 = "" + row_3 = "" + row_3_c = [[0,63,105],[255,255,255],[64,0,119]] + (0...320).each {|x| + n_barre_1 = 7 - x*7/320 + n_barre_2 = if n_barre_1&1==0 then 0 else 8 - n_barre_1 end + row_1 << yield (*([1,2,0].map{|c| 255 * ((n_barre_1 >> c)&1) })) + row_2 << yield (*([1,2,0].map{|c| 255 * ((n_barre_2 >> c)&1) })) + row_3 << yield (*(row_3_c[x/57] || [0,0,0])) + } + 160.times { picture << row_1 } + 20 .times { picture << row_2 } + 60 .times { picture << row_3 } + picture +end diff --git a/externals/gridflow/format/aalib.c b/externals/gridflow/format/aalib.c new file mode 100644 index 00000000..f014a632 --- /dev/null +++ b/externals/gridflow/format/aalib.c @@ -0,0 +1,168 @@ +/* + $Id: aalib.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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. +*/ + +#include "../base/grid.h.fcs" +#define aa_hardwareparams aa_hardware_params +#include <aalib.h> + +/* MINNOR is a typo in aalib.h, sorry */ +typedef +#if AA_LIB_MINNOR == 2 + int +#else + enum aa_attribute +#endif +AAAttr; + +\class FormatAALib < Format +struct FormatAALib : Format { + aa_context *context; + aa_renderparams *rparams; + int autodraw; /* as for X11 */ + bool raw_mode; + + FormatAALib () : context(0), autodraw(1) {} + + \decl void initialize (Symbol mode, Symbol target); + \decl void close (); + \decl void _0_hidecursor (); + \decl void _0_print (int y, int x, int a, Symbol text); + \decl void _0_draw (); + \decl void _0_autodraw (int autodraw); + \decl void _0_dump (); + \grin 0 int +}; + +GRID_INLET(FormatAALib,0) { + if (!context) RAISE("boo"); + if (in->dim->n != 3) + RAISE("expecting 3 dimensions: rows,columns,channels"); + switch (in->dim->get(2)) { + case 1: raw_mode = false; break; + case 2: raw_mode = true; break; + default: + RAISE("expecting 1 greyscale channel (got %d)",in->dim->get(2)); + } + in->set_factor(in->dim->get(1)*in->dim->get(2)); +} GRID_FLOW { + int f = in->factor(); + if (raw_mode) { + int sx = min(f,aa_scrwidth(context)); + int y = in->dex/f; + while (n) { + if (y>=aa_scrheight(context)) return; + for (int x=0; x<sx; x++) { + context->textbuffer[y*aa_scrwidth(context)+x]=data[x*2+0]; + context->attrbuffer[y*aa_scrwidth(context)+x]=data[x*2+1]; + } + y++; + n-=f; + data+=f; + } + } else { + int sx = min(f,context->imgwidth); + int y = in->dex/f; + while (n) { + if (y>=context->imgheight) return; + for (int x=0; x<sx; x++) aa_putpixel(context,x,y,data[x]); + y++; + n-=f; + data+=f; + } + } +} GRID_FINISH { + if (!raw_mode) { + aa_palette pal; + for (int i=0; i<256; i++) aa_setpalette(pal,i,i,i,i); + aa_renderpalette(context,pal,rparams,0,0, + aa_scrwidth(context),aa_scrheight(context)); + } + if (autodraw==1) aa_flush(context); +} GRID_END + +\def void close () { + if (context) { + aa_close(context); + context=0; + } +} + +\def void _0_hidecursor () { aa_hidemouse(context); } +\def void _0_draw () { aa_flush(context); } +\def void _0_print (int y, int x, int a, Symbol text) { + aa_puts(context,x,y,(AAAttr)a,(char *)rb_sym_name(text)); + if (autodraw==1) aa_flush(context); +} +\def void _0_autodraw (int autodraw) { + if (autodraw<0 || autodraw>1) + RAISE("autodraw=%d is out of range",autodraw); + this->autodraw = autodraw; +} +\def void _0_dump () { + int32 v[] = {aa_scrheight(context), aa_scrwidth(context), 2}; + GridOutlet out(this,0,new Dim(3,v)); + for (int y=0; y<aa_scrheight(context); y++) { + for (int x=0; x<aa_scrwidth(context); x++) { + STACK_ARRAY(int32,data,2); + data[0] = context->textbuffer[y*aa_scrwidth(context)+x]; + data[1] = context->attrbuffer[y*aa_scrwidth(context)+x]; + out.send(2,data); + } + } +} + +/* !@#$ varargs missing here */ +\def void initialize (Symbol mode, Symbol target) { + rb_call_super(argc,argv); + argc-=2; argv+=2; + char *argv2[argc]; + for (int i=0; i<argc; i++) + argv2[i] = strdup(rb_str_ptr(rb_funcall(argv[i],SI(to_s),0))); + if (mode!=SYM(out)) RAISE("write-only, sorry"); + aa_parseoptions(0,0,&argc,argv2); + for (int i=0; i<argc; i++) free(argv2[i]); + Ruby drivers = rb_ivar_get(rb_obj_class(rself),SI(@drivers)); + Ruby driver_address = rb_hash_aref(drivers,target); + if (driver_address==Qnil) + RAISE("unknown aalib driver '%s'",rb_sym_name(target)); + aa_driver *driver = FIX2PTR(aa_driver,driver_address); + context = aa_init(driver,&aa_defparams,0); + rparams = aa_getrenderparams(); + if (!context) RAISE("opening aalib didn't work"); + int32 v[]={context->imgheight,context->imgwidth,1}; + gfpost("aalib image size: %s",(new Dim(3,v))->to_s()); +} + +\classinfo { + Ruby drivers = rb_ivar_set(rself,SI(@drivers),rb_hash_new()); + const aa_driver *const *p = aa_drivers; + for (; *p; p++) { + rb_hash_aset(drivers,ID2SYM(rb_intern((*p)->shortname)), PTR2FIX(*p)); + } +// IEVAL(rself,"GridFlow.post('aalib supports: %s', @drivers.keys.join(', '))"); + IEVAL(rself,"install '#in:aalib',1,1;@flags=2;@comment='Ascii Art Library'"); +} +\end class FormatAALib +void startup_aalib () { + \startall +} diff --git a/externals/gridflow/format/dc1394.c b/externals/gridflow/format/dc1394.c new file mode 100644 index 00000000..1632bef7 --- /dev/null +++ b/externals/gridflow/format/dc1394.c @@ -0,0 +1,346 @@ +/* + $Id: dc1394.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include <libraw1394/raw1394.h> +#include <libdc1394/dc1394_control.h> +#include "../base/grid.h.fcs" + +typedef raw1394handle_t RH; +typedef nodeid_t NID; + +static const int ruby_lineno = __LINE__; +static const char *ruby_code = +\ruby + +def choice(*)end +class CStruct + def initialize(*) end +end + +choice :name,:Speed,:start,0,:values,%w(SPEED_100 SPEED_200 SPEED_400) +choice :name,:Framerate,:start,32,:values, +%w(FRAMERATE_1_875 FRAMERATE_3_75 FRAMERATE_7_5 FRAMERATE_15 + FRAMERATE_30 FRAMERATE_60) + +choice :name,:Format0Mode,:start,64,:values,%w( + MODE_160x120_YUV444 MODE_320x240_YUV422 + MODE_640x480_YUV411 MODE_640x480_YUV422 + MODE_640x480_RGB MODE_640x480_MONO + MODE_640x480_MONO16) + +choice :name,:Format1Mode,:start,96,:values,%w( + MODE_800x600_YUV422 MODE_800x600_RGB + MODE_800x600_MONO MODE_1024x768_YUV422 + MODE_1024x768_RGB MODE_1024x768_MONO + MODE_800x600_MONO16 MODE_1024x768_MONO16) + +choice :name,:Format2Mode,:start,128,:values,%w( + MODE_1280x960_YUV422 MODE_1280x960_RGB + MODE_1280x960_MONO MODE_1600x1200_YUV422 + MODE_1600x1200_RGB MODE_1600x1200_MONO + MODE_1280x960_MONO16 MODE_1600x1200_MONO16) + +choice :name,:Format6Mode,:start,256,:values,%w(MODE_EXIF) + +choice :name,:Format7Mode,:start,288,:values,%w( + MODE_FORMAT7_0 MODE_FORMAT7_1 MODE_FORMAT7_2 MODE_FORMAT7_3 + MODE_FORMAT7_4 MODE_FORMAT7_5 MODE_FORMAT7_6 MODE_FORMAT7_7) + +choice :name,:Format7ColorMode,:start,320,:values,%w( + COLOR_FORMAT7_MONO8 COLOR_FORMAT7_YUV411 + COLOR_FORMAT7_YUV422 COLOR_FORMAT7_YUV444 + COLOR_FORMAT7_RGB8 COLOR_FORMAT7_MONO16 + COLOR_FORMAT7_RGB16) + +choice :name,:TriggerMode,:start,352,:values,%w( + TRIGGER_MODE_0 TRIGGER_MODE_1 TRIGGER_MODE_2 TRIGGER_MODE_3) + +choice :name,:CameraImageFormat,:start,384,:values,%w( + FORMAT_VGA_NONCOMPRESSED FORMAT_SVGA_NONCOMPRESSED_1 + FORMAT_SVGA_NONCOMPRESSED_2 skip 3 + FORMAT_STILL_IMAGE FORMAT_SCALABLE_IMAGE_SIZE) + +choice :name,:CameraFeatures,:start,416,:values,%w( + FEATURE_BRIGHTNESS FEATURE_EXPOSURE + FEATURE_SHARPNESS FEATURE_WHITE_BALANCE + FEATURE_HUE FEATURE_SATURATION + FEATURE_GAMMA FEATURE_SHUTTER + FEATURE_GAIN FEATURE_IRIS + FEATURE_FOCUS FEATURE_TEMPERATURE + FEATURE_TRIGGER skip 19 + FEATURE_ZOOM FEATURE_PAN + FEATURE_TILT FEATURE_OPTICAL_FILTER + skip 12 FEATURE_CAPTURE_SIZE + FEATURE_CAPTURE_QUALITY skip 14) + +choice :name,:DCBool,:start,0,:values,%w(False True) + +#define MAX_CHARS 32 +#define SUCCESS 1 +#define FAILURE -1 +#define NO_CAMERA 0xffff + +# Parameter flags for setup_format7_capture() +#define QUERY_FROM_CAMERA -1 +#define USE_MAX_AVAIL -2 +#define USE_RECOMMENDED -3 + +# all dc1394_ prefixes removed +# raw1394handle_t = RH +# nodeid_t = NID +# RH+NID = RN + +CameraInfo = CStruct.new %{ + RH rh; + NID id; + octlet_t ccr_offset; + u_int64_t euid_64; + char vendor[MAX_CHARS + 1]; + char model[MAX_CHARS + 1]; +} + +CameraCapture = CStruct.new %{ + NID node; + int channel, frame_rate, frame_width, frame_height; + int * capture_buffer; + int quadlets_per_frame, quadlets_per_packet; + const unsigned char * dma_ring_buffer; + int dma_buffer_size, dma_frame_size, num_dma_buffers, dma_last_buffer; + const char * dma_device_file; + int dma_fd, port; + struct timeval filltime; + int dma_extra_count; + unsigned char * dma_extra_buffer; + int drop_frames; +} + +MiscInfo = CStruct.new %{ + int format, mode, framerate; + bool is_iso_on; + int iso_channel, iso_speed, mem_channel_number; + int save_channel, load_channel; +} + +FeatureInfo = CStruct.new %{ + uint feature_id; + bool available, one_push, readout_capable, on_off_capable; + bool auto_capable, manual_capable, polarity_capable, one_push_active; + bool is_on, auto_active; + char trigger_mode_capable_mask; + int trigger_mode; + bool trigger_polarity; + int min, max, value, BU_value, RV_value, target_value; +} + +FeatureSet = CStruct.new %{ + FeatureInfo feature[NUM_FEATURES]; +} +#void print_feature_set(FeatureSet *features); +#extern const char *feature_desc[NUM_FEATURES]; +#void print_feature(FeatureInfo *feature); +#RawFire create_handle(int port); +#destroy_handle(RH rh); +#void print_camera_info(camerainfo *info); + +class RH; %{ + # those two: + # Return -1 in numCameras and NULL from the call if there is a problem + # otherwise the number of cameras and the NID array from the call + NID* get_camera_nodes(int *numCameras, int showCameras); + NID* get_sorted_camera_nodes(int numids, int *ids, int *numCameras, int showCameras); + release_camera(CameraCapture *camera); + single_capture(CameraCapture *camera); +# this one returns FAILURE or SUCCESS + multi_capture(CameraCapture *cams, int num); +}end + +class RN; %{ + init_camera(); + is_camera(bool *value); + get_camera_feature_set(FeatureSetFeatureInfo *features); + get_camera_feature(FeatureInfo *feature); + get_camera_misc_info(miscinfo *info); + get_sw_version(quadlet_t *value); + get_camera_info(camerainfo *info); + query_supported_formats(quadlet_t *value); + query_supported_modes(uint format, quadlet_t *value); + query_supported_framerates(uint format, uint mode, quadlet_t *value); + query_revision(int mode, quadlet_t *value); + query_basic_functionality(quadlet_t *value); + query_advanced_feature_offset(quadlet_t *value); + attr set_video_framerate(uint framerate); + attr video_mode(uint mode); + attr video_format(uint format); + double_attr iso_channel_and_speed(uint channel, uint speed); + camera_on(); + camera_off(); + start_iso_transmission(); + stop_iso_transmission(); + get_iso_status(bool *is_on); + set_one_shot(); + unset_one_shot(); + set_multi_shot(uint numFrames); + unset_multi_shot(); +# attributes : +# those are get_/set_ methods where the get has an input parameter +# and the set has an output parameter + attr uint brightness + attr uint exposure + attr uint sharpness + double_attr set_white_balance(uint u_b_value, uint v_r_value); + attr uint hue + attr uint saturation(uint saturation); + attr uint gamma(uint gamma); + attr shutter(uint shutter); + attr uint gain + attr uint iris + attr uint focus + attr uint trigger_mode + attr uint zoom + attr uint pan + attr uint tilt + attr uint optical_filter + attr uint capture_size + attr uint capture_quality + int get_temperature(uint *target_temperature, uint *temperature); + int set_temperature(uint target_temperature); + + get_memory_load_ch(uint *channel); + get_memory_save_ch(uint *channel); + is_memory_save_in_operation(bool *value); + set_memory_save_ch(uint channel); + memory_save(); + memory_load(uint channel); + attr bool trigger_polarity + trigger_has_polarity(bool *polarity); + attr bool set_trigger_on_off + +# this one returns SUCCESS on success, FAILURE otherwise + setup_capture( + int channel, int format, int mode, int speed, int frame_rate, + CameraCapture *camera); + dma_setup_capture( + int channel, int format, int mode, int speed, int frame_rate, + int num_dma_buffers, int drop_frames, const char *dma_device_file, + CameraCapture *camera); + setup_format7_capture( + int channel, int mode, int speed, int bytes_per_packet, + uint left, uint top, uint width, uint height, CameraCapture *camera); + dma_setup_format7_capture( + int channel, int mode, int speed, int bytes_per_packet, + uint left, uint top, uint width, uint height, + int num_dma_buffers, CameraCapture *camera); +}end + +#RNF = RN+uint feature +class RNF; %{ + query_feature_control(uint *availability); + query_feature_characteristics(quadlet_t *value); + attr uint feature_value + is_feature_present(bool *value); + has_one_push_auto(bool *value); + is_one_push_in_operation(bool *value); + start_one_push_operation(); + can_read_out(bool *value); + can_turn_on_off(bool *value); + is_feature_on(bool *value); + feature_on_off(uint value); + has_auto_mode(bool *value); + has_manual_mode(bool *value); + is_feature_auto(bool *value); + auto_on_off(uint value); + get_min_value(uint *value); + get_max_value(uint *value); +}end + +# DMA Capture Functions +#dma_release_camera(RH rh, CameraCapture *camera); +#dma_unlisten(RH rh, CameraCapture *camera); +#dma_single_capture(CameraCapture *camera); +#dma_multi_capture(CameraCapture *cams,int num); +#dma_done_with_buffer(CameraCapture * camera); + +# default return type is int, prolly means SUCCESS/FAILURE + +#RNM = RN+uint mode +class RNM; %{ + query_format7_max_image_size(uint *horizontal_size, uint *vertical_size); + query_format7_unit_size(uint *horizontal_unit, uint *vertical_unit); + query_format7_color_coding(quadlet_t *value); + query_format7_pixel_number(uint *pixnum); + query_format7_total_bytes(uint *total_bytes); + query_format7_packet_para(uint *min_bytes, uint *max_bytes); + query_format7_recommended_byte_per_packet(uint *bpp); + query_format7_packet_per_frame(uint *ppf); + query_format7_unit_position(uint *horizontal_pos, uint *vertical_pos); + # those were query/set pairs. + double_qattr format7_image_position(uint left, uint top); + double_qattr format7_image_size(uint width, uint height); + qattr uint format7_color_coding_id + qattr uint format7_byte_per_packet + query_format7_value_setting(uint *present, uint *setting1, uint *err_flag1, uint *err_flag2); + set_format7_value_setting(); //huh? +}end + +\end ruby +; + +\class FormatDC1394 < Format +struct FormatDC1394 : Format { + \decl void initialize (Symbol mode); + \decl void frame (); +}; + +\def void initialize(Symbol mode) { + gfpost("DC1394: hello world"); + RH rh = raw1394_new_handle(); + int numPorts = raw1394_get_port_info(rh,0,0); + raw1394_destroy_handle(rh); + gfpost("there are %d Feuerweuer ports",numPorts); + if (mode!=SYM(in)) RAISE("sorry, read-only"); + for(int port=0; port<numPorts; port++) { + gfpost("trying port #%d...",port); + RH rh = dc1394_create_handle(port); + int numCameras=0xDEADBEEF; + NID *nodes = dc1394_get_camera_nodes(rh,&numCameras,0); + gfpost("port #%d has %d cameras",port,numCameras); + for (int i=0; i<numCameras; i++) gfpost("camera at node #%d",nodes[i]); + // I'm stuck here, can't find that iSight camera. -- matju + } + dc1394_destroy_handle(rh); +} + +\def void frame () { + gfpost("i'd like to get a frame from the cam, but how?"); +} + +\classinfo { + IEVAL(rself,"install '#io:dc1394',1,1;@flags=4;@comment='Video4linux 1.x'"); + //IEVAL(rself,ruby_code); + rb_funcall(rself,SI(instance_eval),3,rb_str_new2(ruby_code), + rb_str_new2(__FILE__),INT2NUM(ruby_lineno+3)); +} +\end class FormatDC1394 +void startup_dc1394 () { + \startall +} diff --git a/externals/gridflow/format/jpeg.c b/externals/gridflow/format/jpeg.c new file mode 100644 index 00000000..e59d8e06 --- /dev/null +++ b/externals/gridflow/format/jpeg.c @@ -0,0 +1,130 @@ +/* + $Id: jpeg.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +//!@#$ not handling abort on compress +//!@#$ not handling abort on decompress + +#include "../base/grid.h.fcs" +/* removing macros (removing warnings) */ +#undef HAVE_PROTOTYPES +#undef HAVE_STDLIB_H +#undef EXTERN +extern "C" { +#include <jpeglib.h> +}; + +\class FormatJPEG < Format +struct FormatJPEG : Format { + P<BitPacking> bit_packing; + struct jpeg_compress_struct cjpeg; + struct jpeg_decompress_struct djpeg; + struct jpeg_error_mgr jerr; + int fd; + FILE *f; + \decl Ruby frame (); + \decl void initialize (Symbol mode, Symbol source, String filename); + \grin 0 int +}; + +GRID_INLET(FormatJPEG,0) { + if (in->dim->n != 3) + RAISE("expecting 3 dimensions: rows,columns,channels"); + if (in->dim->get(2) != 3) + RAISE("expecting 3 channels (got %d)",in->dim->get(2)); + in->set_factor(in->dim->get(1)*in->dim->get(2)); + cjpeg.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cjpeg); + jpeg_stdio_dest(&cjpeg,f); + cjpeg.image_width = in->dim->get(1); + cjpeg.image_height = in->dim->get(0); + cjpeg.input_components = 3; + cjpeg.in_color_space = JCS_RGB; + jpeg_set_defaults(&cjpeg); + jpeg_start_compress(&cjpeg,TRUE); +} GRID_FLOW { + int rowsize = in->dim->get(1)*in->dim->get(2); + int rowsize2 = in->dim->get(1)*3; + uint8 row[rowsize2]; + uint8 *rows[1] = { row }; + while (n) { + bit_packing->pack(in->dim->get(1),data,Pt<uint8>(row,rowsize)); + jpeg_write_scanlines(&cjpeg,rows,1); + n-=rowsize; data+=rowsize; + } +} GRID_FINISH { + jpeg_finish_compress(&cjpeg); + jpeg_destroy_compress(&cjpeg); +} GRID_END + +static bool gfeof(FILE *f) { + off_t cur,end; + cur = ftell(f); + fseek(f,0,SEEK_END); + end = ftell(f); + fseek(f,cur,SEEK_SET); + return cur==end; +} + +\def Ruby frame () { + off_t off = NUM2LONG(rb_funcall(rb_ivar_get(rself,SI(@stream)),SI(tell),0)); + fseek(f,off,SEEK_SET); + if (gfeof(f)) return Qfalse; + djpeg.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&djpeg); + jpeg_stdio_src(&djpeg,f); + jpeg_read_header(&djpeg,TRUE); + int sx=djpeg.image_width, sy=djpeg.image_height, chans=djpeg.num_components; + GridOutlet out(this,0,new Dim(sy, sx, chans), + NumberTypeE_find(rb_ivar_get(rself,SI(@cast)))); + jpeg_start_decompress(&djpeg); + uint8 row[sx*chans]; + uint8 *rows[1] = { row }; + for (int n=0; n<sy; n++) { + jpeg_read_scanlines(&djpeg,rows,1); + out.send(sx*chans,Pt<uint8>(row,sx*chans)); + } + jpeg_finish_decompress(&djpeg); + jpeg_destroy_decompress(&djpeg); + return Qnil; +} + +\def void initialize (Symbol mode, Symbol source, String filename) { + rb_call_super(argc,argv); + if (source!=SYM(file)) RAISE("usage: jpeg file <filename>"); + rb_funcall(rself,SI(raw_open),3,mode,source,filename); + Ruby stream = rb_ivar_get(rself,SI(@stream)); + fd = NUM2INT(rb_funcall(stream,SI(fileno),0)); + f = fdopen(fd,mode==SYM(in)?"r":"w"); + uint32 mask[3] = {0x0000ff,0x00ff00,0xff0000}; + bit_packing = new BitPacking(is_le(),3,3,mask); +} + +\classinfo { + IEVAL(rself, + "install '#in:jpeg',1,1;@mode=6;" + "include GridFlow::EventIO; suffixes_are'jpeg','jpg'"); +} +\end class FormatJPEG +void startup_jpeg () { + \startall +} diff --git a/externals/gridflow/format/main.rb b/externals/gridflow/format/main.rb new file mode 100644 index 00000000..6e31be75 --- /dev/null +++ b/externals/gridflow/format/main.rb @@ -0,0 +1,788 @@ +=begin + $Id: main.rb,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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 + +require "socket" +require "fcntl" + +module GridFlow + +class<<self + def max_rank; 16; end + def max_size; 64*1024**2; end + def max_packet; 1024*2; end +end + +ENDIAN_BIG,ENDIAN_LITTLE,ENDIAN_SAME,ENDIAN_DIFF = 0,1,2,3 + +OurByteOrder = case [1].pack("L") + when "\0\0\0\1"; ENDIAN_BIG # Mac, Sun, SiliconGraphics + when "\1\0\0\0"; ENDIAN_LITTLE # Intel + else raise "Cannot determine byte order" end + +class Format < GridObject + FF_R,FF_W = 4,2 # flags indicating support of :in and :out respectively. + attr_accessor :parent +=begin API (version 0.8) + mode is :in or :out + def initialize(mode,*args) : + open a file handler (do it via .new of class) + attr_reader :description : + a _literal_ (constant) string describing the format handler + def self.info() optional : + return a string describing the format handler differently + than self.description(). in particular, it can list + compile-time options and similar things. for example, + quicktime returns a list of codecs. + def frame() : + read one frame, send through outlet 0 + return values : + Integer >= 0 : frame number of frame read. + false : no frame was read : end of sequence. + nil : a frame was read, but can't say its number. + note that trying to read a nonexistent frame should no longer + rewind automatically (@in handles that part), nor re-read the + last frame (mpeg/quicktime used to do this) + def seek(Integer i) : select one frame to be read next (by number) + def length() : ^Integer returns number of frames (never implemented ?) + def close() : close a handler + inlet 0 : + grid : frame to write + other : special options + outlet 0 : grid : frame just read + outlet 1 : everything else +=end + + def initialize(mode,*) + super + @cast = :int32 + @colorspace = :rgb + @mode = mode + @frame = 0 + @parent = nil + @stream = nil + flags = self.class.instance_eval{if defined?@flags then @flags else 6 end} + # FF_W, FF_R, FF_RW + case mode + when :in; flags[2]==1 + when :out; flags[1]==1 + else raise "Format opening mode is incorrect" + end or raise \ + "Format '#{self.class.instance_eval{@symbol_name}}'"\ + " does not support mode '#{mode}'" + end + + def close + @stream.close if defined? @stream and @stream + end + + def self.suffixes_are(*suffixes) + suffixes.map{|s|s.split(/[, ]/)}.flatten.each {|suffix| + Format.suffixes[suffix] = self + } + end + + class<<self + attr_reader :symbol_name + attr_reader :description + attr_reader :flags + attr_reader :suffixes + end + @suffixes = {} + def seek frame + (rewind; return) if frame == 0 + raise "don't know how to seek for frame other than # 0" + end + + # this is what you should use to rewind + # different file-sources may redefine this as something else + # (eg: gzip) + def rewind + raise "Nothing to rewind about..." if not @stream + @stream.seek 0,IO::SEEK_SET + @frame = 0 + end + + # This is different from IO#eof, which waits until a read has failed + # doesn't work in nonblocking mode? (I don't recall why) + def eof? + thispos = (@stream.seek 0,IO::SEEK_CUR; @stream.tell) + lastpos = (@stream.seek 0,IO::SEEK_END; @stream.tell) + @stream.seek thispos,IO::SEEK_SET + return thispos == lastpos + rescue Errno::ESPIPE # just ignore if seek is not possible + return false + end + + # "ideal" buffer size or something + # the buffer may be bigger than this but usually not by much. + def self.buffersize; 16384 end + + def _0_headerless(*args) #!@#$ goes in FormatGrid ? + args=args[0] if Array===args[0] + #raise "expecting dimension list..." + args.map! {|a| + Numeric===a or raise "expecting dimension list..." + a.to_i + } + @headerless = args + end + def _0_headerful #!@#$ goes in FormatGrid ? + @headerless = nil + end + def _0_type arg + #!@#$ goes in FormatGrid ? + #!@#$ bug: should not be able to modify this _during_ a transfer + case arg + when :uint8; @bpv=8; @bp=BitPacking.new(ENDIAN_LITTLE,1,[0xff]) + when :int16; @bpv=16; @bp=BitPacking.new(ENDIAN_LITTLE,1,[0xffff]) + when :int32; @bpv=32; @bp=nil + else raise "unsupported number type: #{arg}" + end + end + def _0_cast arg + case arg + when :uint8, :int16, :int32, :int64, :float32, :float64 + @cast = arg + else raise "unsupported number type: #{arg}" + end + end + def frame; @frame+=1; @frame-1 end +end + +# common parts between GridIn and GridOut +module GridIO + def check_file_open; if not @format then raise "can't do that: file not open" end end + def _0_close; check_file_open; @format.close; @format = nil end + def delete; @format.close if @format; @format = nil; super end + attr_reader :format + + def _0_open(sym,*a) + sym = sym.intern if String===sym + if a.length==0 and /\./ =~ sym.to_s then a=[sym]; sym=:file end + qlass = GridFlow.fclasses["\#io:#{sym}"] + if not qlass then raise "unknown file format identifier: #{sym}" end + _0_close if @format + @format = qlass.new @mode, *a + @format.connect 0,self,1 + @format.connect 1,self,2 + @format.parent = self + @loop = true + end + + def _0_timelog flag; @timelog = Integer(flag)!=0 end + def _0_loop flag; @loop = Integer(flag)!=0 end + def method_missing(*message) + sel = message[0].to_s + if sel =~ /^_0_/ + message[0] = sel.sub(/^_0_/,"").intern + @format.send_in 0, *message + elsif sel =~ /^_2_/ + sel = sel.sub(/^_2_/,"").intern + message.shift + send_out 1, sel, *message + else + return super + end + end +end + +GridObject.subclass("#in",1,2) { + install_rgrid 0 + include GridIO + def initialize(*a) + super + @format = nil + @timelog = false + @framecount = 0 + @time = Time.new + @mode = :in + return if a.length==0 + _0_open(*a) + end + def _0_bang + check_file_open + framenum = @format.frame + if framenum == false + send_out 1 + return if not @loop + @format.seek 0 + framenum = @format.frame + if framenum == false + raise "can't read frame: the end is at the beginning???" + end + end + send_out 1, framenum if framenum + end + def _0_float frame; _0_set frame; _0_bang end + def _0_set frame; check_file_open; @format.seek frame end + def _0_reset; check_file_open; @format.seek 0; end + def _1_grid(*a) send_out 0,:grid,*a end + def _0_load name; _0_open name; _0_bang; _0_close end +} + +GridObject.subclass("#out",1,1) { + include GridIO + def initialize(*a) + super + @format = nil + @timelog = false + @framecount = 0 + @time = Time.new + @mode = :out + return if a.length==0 + if Integer===a[0] or Float===a[0] + _0_open :x11,:here + _0_out_size a[0],a[1] + else + _0_open(*a) + end + end + + def _0_list(*a) @format._0_list(*a) end + + # hacks + def _1_grid(*a) send_out 0,:grid,*a end # for aalib + def _1_position(*a) send_out 0,:position,*a end + def _1_keypress(*a) send_out 0,:keypress,*a end + def _1_keyrelease(*a) send_out 0,:keyrelease,*a end + + def _0_grid(*a) + check_file_open + @format._0_grid(*a) + send_out 0,:bang + log if @timelog + @framecount+=1 + end + + def log + time = Time.new + post("\#out: frame#%04d time: %10.3f s; diff: %5d ms", + @framecount, time, ((time-@time)*1000).to_i) + @time = time + end + install_rgrid 0 +} + +class BitPacking + alias pack pack2 + alias unpack unpack2 +end + +# adding event-driven IO to a Format class +module EventIO + def read_wait?; !!@action; end + + def initialize(*) + @acceptor = nil + @buffer = nil + @action = nil + @chunksize = nil + @rewind_redefined = false + @clock = Clock.new self + @delay = 100 # ms + super + end + + def call() try_read end + + def on_read(n,&action) + @action = action + @chunksize = n + end + + def try_accept + #!@#$ use setsockopt(SO_REUSEADDR) here??? + TCPSocket.do_not_reverse_lookup = true # hack + @acceptor.nonblock = true + @stream = @acceptor.accept + @stream.nonblock = true + @stream.sync = true + @clock.unset +# send_out 0, :accept # does not work + rescue Errno::EAGAIN + end + + def try_read(dummy=nil) + n = @chunksize-(if @buffer then @buffer.length else 0 end) + t = @stream.read(n) # or raise EOFError + if not t + raise "heck" if not @stream.eof? + rewind + t = @stream.read(n) or raise "can't read any of #{n} bytes?" + end + if @buffer then @buffer << t else @buffer = t end + if @buffer.length == @chunksize + action,buffer = @action,@buffer + @action,@buffer = nil,"" + @clock.unset + action.call buffer + end + rescue Errno::EAGAIN + post "read would block" + end + + def raw_open_gzip_in(filename) + r,w = IO.pipe + if pid=fork + GridFlow.subprocesses[pid]=true + w.close + @stream = r + else + r.close + STDOUT.reopen w + STDIN.reopen @stream + @stream = File.open filename, "r" + exec "gzip", "-dc" + end + end + def raw_open_gzip_out(filename) + r,w = IO.pipe + if pid=fork + GridFlow.subprocesses[pid]=true + r.close + @stream = w + else + w.close + STDIN.reopen r + STDOUT.reopen @stream + @stream = File.open filename, "w" + exec "gzip", "-c" + end + end + def raw_open(mode,source,*args) + @raw_open_args = mode,source,*args + fmode = case mode + when :in; "r" + when :out; "w" + else raise "bad mode" end + close + case source + when :file + filename = args[0].to_s + filename = GridFlow.find_file filename if mode==:in + @stream = File.open filename, fmode + when :gzfile + filename = args[0].to_s + filename = GridFlow.find_file filename if mode==:in + if mode==:in then + raw_open_gzip_in filename + else + raw_open_gzip_out filename + end + def self.rewind + raw_open(*@raw_open_args) + @frame = 0 + end unless @rewind_redefined + @rewind_redefined = true + when :tcp + if RUBY_VERSION < "1.6.6" + raise "use at least 1.6.6 (reason: bug in socket code)" + end + post "-----------" + time = Time.new + TCPSocket.do_not_reverse_lookup = true # hack + @stream = TCPSocket.open(args[0].to_s,args[1].to_i) + post "----------- #{Time.new-time}" + @stream.nonblock = true + @stream.sync = true + @clock.delay @delay + when :tcpserver + TCPSocket.do_not_reverse_lookup = true # hack + TCPServer.do_not_reverse_lookup = true # hack + post "-----------" + time = Time.new + @acceptor = TCPServer.open(args[0].to_s) + post "----------- #{Time.new-time}" + @acceptor.nonblock = true + #$tasks[self] = proc {self.try_accept} #!!!!! + else + raise "unknown access method '#{source}'" + end + end + def close + @acceptor.close if @acceptor + @stream.close if @stream + GridFlow.hunt_zombies + end +end + +Format.subclass("#io:file",1,1) { + def self.new(mode,file) + file=file.to_s + a = [mode,:file,file] + if not /\./=~file then raise "no filename suffix?" end + suf=file.split(/\./)[-1] + h=Format.suffixes[suf] + if not h then raise "unknown suffix '.#{suf}'" end + h.new(*a) + end + @comment="format autodetection proxy" +} + +Format.subclass("#io:grid",1,1) { + include EventIO + install_rgrid 0 + @comment = "GridFlow file format" + suffixes_are "grid" +=begin + This is the Grid format I defined: + 1 uint8: 0x7f + 4 uint8: "GRID" big endian | "grid" little endian + 1 uint8: type { + number of bits in 8,16,32,64, plus one of: 1:unsigned 2:float + but float8,float16 are not allowed (!) + } + 1 uint8: reserved (supported: 0) + 1 uint8: number of dimensions N (supported: at least 0..4) + N uint32: number of elements per dimension D[0]..D[N-1] + raw data goes there. +=end + # bits per value: 32 only + attr_accessor :bpv # Fixnum: bits-per-value + # endianness + # attr_accessor :endian # ENDIAN_LITTLE or ENDIAN_BIG + # IO or File or TCPSocket + attr_reader :stream + # nil=headerful; array=assumed dimensions of received grids + #attr_accessor :headerless + + def initialize(mode,source,*args) + super + @bpv = 32 + @headerless = nil + @endian = OurByteOrder + raw_open mode,source,*args + end + + def post(*s) + # because i'm using miller_0_38 and it can't disable the console + # i am using fprintf stderr instead of post. + ### STDERR.puts(sprintf(*s)) + # disabled because i don't need it now + end + + # rewinding and starting + def frame + raise "can't get frame when there is no connection" if not @stream + raise "already waiting for input" if read_wait? + return false if eof? + post "----- 1" + if @headerless then + @n_dim=@headerless.length + @dim = @headerless + @dex = 0 + set_bufsize + send_out_grid_begin 0, @dim + on_read(bufsize) {|data| frame3 data } + else + on_read(8) {|data| frame1 data } + end + post "----- 2" + (try_read nil while read_wait?) if not TCPSocket===@stream + post "----- 3" + super + post "----- 4" + end + + def set_bufsize + @prod = 1 + @dim.each {|x| @prod *= x } + n = @prod/@dim[0] + k = GridFlow.max_packet / n + k=1 if k<1 + @bufsize = k*n*@bpv/8 + @bufsize = @prod if @bufsize > @prod + end + + # the header + def frame1 data + post "----- frame1" + head,@bpv,reserved,@n_dim = data.unpack "a5ccc" + @endian = case head + when "\x7fGRID"; ENDIAN_BIG + when "\x7fgrid"; ENDIAN_LITTLE + else raise "grid header: invalid (#{data.inspect})" end + case bpv + when 8, 16, 32; # ok + else raise "unsupported bpv (#{@bpv})" + end + if reserved!=0 + raise "reserved field is not zero" + end + if @n_dim > GridFlow.max_rank + raise "too many dimensions (#{@n_dim})" + end + on_read(4*@n_dim) {|data| frame2 data } + end + + # the dimension list + def frame2 data + post "----- frame2" + @dim = data.unpack(if @endian==ENDIAN_LITTLE then "V*" else "N*" end) + set_bufsize + if @prod > GridFlow.max_size + raise "dimension list: invalid prod (#{@prod})" + end + send_out_grid_begin 0, @dim, @cast + + on_read(bufsize) {|data| frame3 data } + @dex = 0 + end + + attr_reader :bufsize + + # for each slice of the body + def frame3 data + post "----- frame3 with dex=#{@dex.inspect}, prod=#{@prod.inspect}" + n = data.length + nn = n*8/@bpv + # is/was there a problem with the size of the data being read? + case @bpv + when 8 + @bp = BitPacking.new(@endian,1,[0xff]) + send_out_grid_flow(0, @bp.unpack(data)) + @dex += data.length + when 16 + @bp = BitPacking.new(@endian,2,[0xffff]) + send_out_grid_flow(0, @bp.unpack(data)) + @dex += data.length/2 + when 32 + data.swap32! if @endian!=OurByteOrder + send_out_grid_flow 0, data + @dex += data.length/4 + end + if @dex >= @prod + @clock.unset + else + on_read(bufsize) {|data| frame3 data } + end + end + + def _0_rgrid_begin + if not @stream + raise "can't send frame when there is no connection" + end + @dim = inlet_dim 0 + post "@dim=#{@dim.inspect}" + return if @headerless + # header + @stream.write( + [if @endian==ENDIAN_LITTLE then "\x7fgrid" else "\x7fGRID" end, + @bpv,0,@dim.length].pack("a5ccc")) + # dimension list + @stream.write( + @dim.to_a.pack(if @endian==ENDIAN_LITTLE then "V*" else "N*" end)) + end + + def _0_rgrid_flow data + case @bpv + when 8, 16 + @stream.write @bp.pack(data) + when 32 + data.swap32! if GridFlow::OurByteOrder != @endian + @stream.write data + end + end + + def _0_rgrid_end; @stream.flush end + + def endian(a) + @endian = case a + when :little; ENDIAN_LITTLE + when :big; ENDIAN_BIG + when :same; ENDIAN_SAME + else raise "argh" + end + end + + def headerless(*args) + args=args[0] if Array===args[0] + args.map! {|a| + Numeric===a or raise "expecting dimension list..." + a.to_i + } + @headerless = args + end + + def headerful; @headerless = nil end + + #!@#$ method name conflict ? + def type(nt) + #!@#$ bug: should not be able to modify this _during_ a transfer + case nt + when :uint8; @bpv= 8; @bp=BitPacking.new(ENDIAN_LITTLE,1,[0xff]) + when :int16; @bpv=16; @bp=BitPacking.new(ENDIAN_LITTLE,1,[0xffff]) + when :int32; @bpv=32; @bp=nil + else raise "unsupported number type" + end + end +} + +module PPMandTarga + # "and false" disables features that may cause crashes and don't + # accelerate gridflow that much. + def frame_read_body height, width, channels + bs = width*channels + n = bs*height + bs = (self.class.buffersize/bs)*bs+bs # smallest multiple of bs over BufferSize + buf = "" + if RUBY_VERSION >= "1.8.0" and false + data = "x"*bs # must preallocate (bug in 1.8.0.pre1-3) + while n>0 do + bs=n if bs>n + @stream.read(bs,data) or raise EOFError + if @bp then + send_out_grid_flow 0, @bp.unpack(data,buf) + else + send_out_grid_flow 0, data, :uint8 + end + n-=bs + end + else + nothing = "" + while n>0 do + bs=n if bs>n + data = @stream.read(bs) or raise EOFError + if @bp then + send_out_grid_flow 0, @bp.unpack(data,buf) + else + send_out_grid_flow 0, data, :uint8 + end + data.replace nothing and false # prevent clogging memory + n-=bs + end + end + end +end + +Format.subclass("#io:ppm",1,1) { + install_rgrid 0 + @comment = "Portable PixMap (PPM) File Format" + suffixes_are "ppm" + include EventIO, PPMandTarga + + def initialize(mode,source,*args) + @bp = if mode==:out + BitPacking.new(ENDIAN_LITTLE,3,[0x0000ff,0x00ff00,0xff0000]) + else nil end + super + raw_open mode,source,*args + end + def frame + #@stream.sync = false + metrics=[] + return false if eof? + line = @stream.gets + (rewind; line = @stream.gets) if not line # hack + line.chomp! + if line != "P6" then raise "Wrong format (needing PPM P6)" end + while metrics.length<3 + line = @stream.gets + next if line =~ /^#/ + metrics.push(*(line.split(/\s+/).map{|x| Integer x })) + end + metrics[2]==255 or + raise "Wrong color depth (max_value=#{metrics[2]} instead of 255)" + + send_out_grid_begin 0, [metrics[1], metrics[0], 3], @cast + frame_read_body metrics[1], metrics[0], 3 + super + end + + def _0_rgrid_begin + dim = inlet_dim 0 + raise "expecting (rows,columns,channels)" if dim.length!=3 + raise "expecting channels=3" if dim[2]!=3 + @stream.write "P6\n" + @stream.write "# generated using GridFlow #{GF_VERSION}\n" + @stream.write "#{dim[1]} #{dim[0]}\n255\n" + @stream.flush + inlet_set_factor 0, 3 + end + def _0_rgrid_flow(data) @stream.write @bp.pack(data) end + def _0_rgrid_end; @stream.flush end +} + +Format.subclass("#io:targa",1,1) { + install_rgrid 0 + @comment = "TrueVision Targa" + suffixes_are "tga" + include EventIO, PPMandTarga +=begin +targa header is like: + [:comment, Uint8, :length], + [:colortype, Uint8], + [:colors, Uint8], 5, + [:origin_x, Int16], + [:origin_y, Int16], + [:w, Uint16], + [:h, Uint16], + [:depth, Uint8], 1, + [:comment, String8Unpadded, :data], +=end + def initialize(mode,source,*args) + super + raw_open mode,source,*args + end + + def set_bitpacking depth + @bp = case depth + #!@#$ endian here doesn't seem to be changing much ? + when 24; BitPacking.new(ENDIAN_LITTLE,3,[0xff0000,0x00ff00,0x0000ff]) + when 32; BitPacking.new(ENDIAN_LITTLE,4, + [0x00ff0000,0x0000ff00,0x000000ff,0xff000000]) + else + raise "tga: unsupported colour depth: #{depth}\n" + end + end + + def frame + return false if eof? + head = @stream.read(18) + comment_length,colortype,colors,w,h,depth = head.unpack("cccx9vvcx") + comment = @stream.read(comment_length) + raise "unsupported color format: #{colors}" if colors != 2 +# post "tga: size y=#{h} x=#{w} depth=#{depth} colortype=#{colortype}" +# post "tga: comment: \"#{comment}\"" + set_bitpacking depth + send_out_grid_begin 0, [ h, w, depth/8 ], @cast + frame_read_body h, w, depth/8 + super + end + + def _0_rgrid_begin + dim = inlet_dim 0 + raise "expecting (rows,columns,channels)" if dim.length!=3 + raise "expecting channels=3 or 4" if dim[2]!=3 and dim[2]!=4 + # comment = "created using GridFlow" + #!@#$ why did i use that comment again? + comment = "generated using GridFlow #{GF_VERSION}" + @stream.write [comment.length,colortype=0,colors=2,"\0"*9, + dim[1],dim[0],8*dim[2],(8*(dim[2]-3))|32,comment].pack("ccca9vvcca*") + set_bitpacking 8*dim[2] + inlet_set_factor 0, dim[2] + end + def _0_rgrid_flow data; @stream.write @bp.pack(data) end + def _0_rgrid_end; @stream.flush end +} +end # module GridFlow diff --git a/externals/gridflow/format/mpeg3.c b/externals/gridflow/format/mpeg3.c new file mode 100644 index 00000000..d6820ccd --- /dev/null +++ b/externals/gridflow/format/mpeg3.c @@ -0,0 +1,98 @@ +/* + $Id: mpeg3.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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. +*/ + +#define LIBMPEG_INCLUDE_HERE +#include "../base/grid.h.fcs" +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +\class FormatMPEG3 < Format +struct FormatMPEG3 : Format { + mpeg3_t *mpeg; + P<BitPacking> bit_packing; + int track; + FormatMPEG3 () : track(0) {} + \decl void initialize (Symbol mode, Symbol source, String filename); + \decl void seek (int frame); + \decl Ruby frame (); + \decl void close (); +}; + +\def void seek (int frame) { mpeg3_set_frame(mpeg,frame,track); } + +\def Ruby frame () { + int nframe = mpeg3_get_frame(mpeg,track); + if (nframe >= mpeg3_video_frames(mpeg,track)) return Qfalse; + + int sx = mpeg3_video_width(mpeg,track); + int sy = mpeg3_video_height(mpeg,track); + int npixels = sx*sy; + int channels = 3; + Pt<uint8> buf = ARRAY_NEW(uint8,sy*sx*channels+16); + uint8 *rows[sy]; + for (int i=0; i<sy; i++) rows[i]=buf+i*sx*channels; + int result = mpeg3_read_frame(mpeg,rows,0,0,sx,sy,sx,sy,MPEG3_RGB888,track); + + GridOutlet out(this,0,new Dim(sy, sx, channels), + NumberTypeE_find(rb_ivar_get(rself,SI(@cast)))); + int bs = out.dim->prod(1); + STACK_ARRAY(int32,b2,bs); + for(int y=0; y<sy; y++) { + Pt<uint8> row = buf+channels*sx*y; + /* bit_packing->unpack(sx,row,b2); out.send(bs,b2); */ + out.send(bs,row); + } + delete[] (uint8 *)buf; + return INT2NUM(nframe); +} + +\def void close () { +// fprintf(stderr, "begin mpeg3_close...\n"); + if (mpeg) { mpeg3_close(mpeg); mpeg=0; } + rb_call_super(argc,argv); +// fprintf(stderr, "end mpeg3_close...\n"); +} + +// libmpeg3 may be nice, but it won't take a filehandle, only filename +\def void initialize (Symbol mode, Symbol source, String filename) { + rb_call_super(argc,argv); + if (mode!=SYM(in)) RAISE("read-only, sorry"); + if (source!=SYM(file)) RAISE("usage: mpeg file <filename>"); + if (TYPE(filename)!=T_STRING) RAISE("PATATE POILUE"); + filename = rb_funcall(mGridFlow,SI(find_file),1,filename); + mpeg = mpeg3_open(rb_str_ptr(filename)); + if (!mpeg) RAISE("IO Error: can't open file `%s': %s", filename, strerror(errno)); + uint32 mask[3] = {0x0000ff,0x00ff00,0xff0000}; + bit_packing = new BitPacking(is_le(),3,3,mask); +} + +\classinfo { + IEVAL(rself,"install '#in:mpeg',1,1;@flags=4;" + "@comment='Motion Picture Expert Group Format" + " (using HeroineWarrior\\'s)';suffixes_are'mpg,mpeg'"); +} +\end class FormatMPEG3 +void startup_mpeg3 () { + \startall +} diff --git a/externals/gridflow/format/png.c b/externals/gridflow/format/png.c new file mode 100644 index 00000000..69a0254e --- /dev/null +++ b/externals/gridflow/format/png.c @@ -0,0 +1,138 @@ +/* + $Id: png.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +/* !@#$ not handling abort on compress */ +/* !@#$ not handling abort on decompress */ + +#include "../base/grid.h.fcs" +extern "C" { +#include <libpng12/png.h> +}; + +\class FormatPNG < Format +struct FormatPNG : Format { + P<BitPacking> bit_packing; + png_structp png; + png_infop info; + int fd; + FILE *f; + FormatPNG () : bit_packing(0), png(0), f(0) {} + \decl Ruby frame (); + \decl void initialize (Symbol mode, Symbol source, String filename); + \grin 0 int +}; + +GRID_INLET(FormatPNG,0) { + if (in->dim->n != 3) + RAISE("expecting 3 dimensions: rows,columns,channels"); + if (in->dim->get(2) != 3) + RAISE("expecting 3 channels (got %d)",in->dim->get(2)); + in->set_factor(in->dim->get(1)*in->dim->get(2)); + RAISE("bother, said pooh, as the PNG encoding was found unimplemented"); +} GRID_FLOW { + int rowsize = in->dim->get(1)*in->dim->get(2); + int rowsize2 = in->dim->get(1)*3; + uint8 row[rowsize2]; + while (n) { + bit_packing->pack(in->dim->get(1),data,Pt<uint8>(row,rowsize)); + n-=rowsize; data+=rowsize; + } +} GRID_FINISH { +} GRID_END + +\def Ruby frame () { + uint8 sig[8]; + if (!fread(sig, 1, 8, f)) return Qfalse; + if (!png_check_sig(sig, 8)) RAISE("bad signature"); + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) RAISE("!png"); + info = png_create_info_struct(png); + if (!info) { + png_destroy_read_struct(&png, NULL, NULL); RAISE("!info"); + } + if (setjmp(png_jmpbuf(png))) { + png_destroy_read_struct(&png, &info, NULL); RAISE("png read error"); + } + png_init_io(png, f); + png_set_sig_bytes(png, 8); // we already read the 8 signature bytes + png_read_info(png, info); // read all PNG info up to image data + png_uint_32 width, height; + int bit_depth, color_type; + png_get_IHDR(png, info, &width, &height, &bit_depth, &color_type, 0,0,0); + + png_bytepp row_pointers = 0; + if (color_type == PNG_COLOR_TYPE_PALETTE + || (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + || png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_expand(png); + // 16bpp y, 32bpp ya, 48bpp rgb, 64bpp rgba... + if (bit_depth == 16) png_set_strip_16(png); + + double display_gamma = 2.2; + double gamma; + if (png_get_gAMA(png, info, &gamma)) + png_set_gamma(png, display_gamma, gamma); + png_read_update_info(png, info); + + int rowbytes = png_get_rowbytes(png, info); + int channels = (int)png_get_channels(png, info); + Pt<uint8> image_data = ARRAY_NEW(uint8,rowbytes*height); + row_pointers = new png_bytep[height]; + //gfpost("png: color_type=%d channels=%d, width=%d, rowbytes=%ld, height=%ld, gamma=%f", + // color_type, channels, width, rowbytes, height, gamma); + for (int i=0; i<(int)height; i++) row_pointers[i] = image_data + i*rowbytes; + if ((uint32)rowbytes != width*channels) + RAISE("rowbytes mismatch: %d is not %d*%d=%d", + rowbytes, width, channels, width*channels); + png_read_image(png, row_pointers); + delete row_pointers; + row_pointers = 0; + png_read_end(png, 0); + + GridOutlet out(this,0,new Dim(height, width, channels), + NumberTypeE_find(rb_ivar_get(rself,SI(@cast)))); + out.send(rowbytes*height,image_data); + free(image_data); + png_destroy_read_struct(&png, &info, NULL); + return Qnil; +} + +\def void initialize (Symbol mode, Symbol source, String filename) { + rb_call_super(argc,argv); + if (source!=SYM(file)) RAISE("usage: png file <filename>"); + rb_funcall(rself,SI(raw_open),3,mode,source,filename); + Ruby stream = rb_ivar_get(rself,SI(@stream)); + fd = NUM2INT(rb_funcall(stream,SI(fileno),0)); + f = fdopen(fd,mode==SYM(in)?"r":"w"); + uint32 mask[3] = {0x0000ff,0x00ff00,0xff0000}; + bit_packing = new BitPacking(is_le(),3,3,mask); +} + +\classinfo { + IEVAL(rself, + "install '#io:png',1,1;@mode=4;include GridFlow::EventIO; suffixes_are'png'"); +} +\end class FormatPNG +void startup_png () { + \startall +} diff --git a/externals/gridflow/format/quicktimeapple.c b/externals/gridflow/format/quicktimeapple.c new file mode 100644 index 00000000..0664322e --- /dev/null +++ b/externals/gridflow/format/quicktimeapple.c @@ -0,0 +1,500 @@ +/* + $Id: quicktimeapple.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#define T_DATA T_COCOA_DATA +#include <Quicktime/Quicktime.h> +#include <Quicktime/Movies.h> +#include <Quicktime/QuickTimeComponents.h> +#undef T_DATA +#include "../base/grid.h.fcs" +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <CoreServices/CoreServices.h> + +typedef ComponentInstance VideoDigitizerComponent, VDC; +typedef ComponentResult VideoDigitizerError, VDE; + +//enum {VDCType='vdig', vdigInterfaceRev=2 }; +//enum {ntscIn=0, currentIn=0, palIn, secamIn, ntscReallyIn }; +//enum {compositeIn, sVideoIn, rgbComponentIn, rgbComponentSyncIn, yuvComponentIn, yuvComponentSyncIn, tvTunerIn, sdiIn}; +//enum {vdPlayThruOff, vdPlayThruOn}; +//enum {vdDigitizerBW, vdDigitizerRGB}; +//enum {vdBroadcastMode, vdVTRMode}; +//enum {vdUseAnyField, vdUseOddField, vdUseEvenField}; +//enum {vdTypeBasic, vdTypeAlpha, vdTypeMask, vdTypeKey}; +/*enum {digiInDoesNTSC, digiInDoesPAL, digiInDoesSECAM, skip 4, + digiInDoesGenLock, digiInDoesComposite, digiInDoesSVideo, digiInDoesComponent, + digiInVTR_Broadcast, digiInDoesColor, digiInDoesBW, skip 17, + digiInSignalLock};*/ +/*bitset {digiOutDoes1, digiOutDoes2, digiOutDoes4, + digiOutDoes8, digiOutDoes16, digiOutDoes32, + digiOutDoesDither, digiOutDoesStretch, digiOutDoesShrink, + digiOutDoesMask, skip 1, + digiOutDoesDouble, digiOutDoesQuad, digiOutDoesQuarter, digiOutDoesSixteenth, + digiOutDoesRotate, digiOutDoesHorizFlip, digiOutDoesVertFlip, digiOutDoesSkew, + digiOutDoesBlend, digiOutDoesWarp, digiOutDoesHW_DMA, + digiOutDoesHWPlayThru, digiOutDoesILUT, digiOutDoesKeyColor, + digiOutDoesAsyncGrabs, digiOutDoesUnreadableScreenBits, + digiOutDoesCompress, digiOutDoesCompressOnly, + digiOutDoesPlayThruDuringCompress, digiOutDoesCompressPartiallyVisible, + digiOutDoesNotNeedCopyOfCompressData};*/ +/*struct DigitizerInfo { + short vdigType; + long inputCapabilityFlags, outputCapabilityFlags; + long inputCurrentFlags, outputCurrentFlags; + short slot; + GDHandle gdh, maskgdh; + short minDestHeight, minDestWidth; + short maxDestHeight, maxDestWidth; + short blendLevels; + long reserved;};*/ +/*struct VdigType { long digType, reserved;};*/ +/*struct VdigTypeList { short count; VdigType list[1];};*/ +/*struct VdigBufferRec { PixMapHandle dest; Point location; long reserved;};*/ +/*struct VdigBufferRecList { + short count; MatrixRecordPtr matrix; RgnHandle mask; VdigBufferRec list[1];};*/ +//typedef VdigBufferRecList *VdigBufferRecListPtr; +//typedef VdigBufferRecListPtr *VdigBufferRecListHandle; +//typedef CALLBACK_API(void,VdigIntProcPtr)(long flags, long refcon); +//typedef STACK_UPP_TYPE(VdigIntProcPtr); +/*struct VDCompressionList { + CodecComponent codec; CodecType cType; Str63 typeName, name; + long formatFlags, compressFlags, reserved;};*/ +//typedef VDCompressionList * VDCompressionListPtr; +//typedef VDCompressionListPtr *VDCompressionListHandle; +/*bitset { + dmaDepth1, dmaDepth2, dmaDepth4, dmaDepth8, dmaDepth16, dmaDepth32, + dmaDepth2Gray, dmaDepth4Gray, dmaDepth8Gray};*/ +//enum {kVDIGControlledFrameRate=-1}; +//bitset {vdDeviceFlagShowInputsAsDevices, vdDeviceFlagHideDevice}; +/*bitset { + vdFlagCaptureStarting, vdFlagCaptureStopping, + vdFlagCaptureIsForPreview, vdFlagCaptureIsForRecord, + vdFlagCaptureLowLatency, vdFlagCaptureAlwaysUseTimeBase, + vdFlagCaptureSetSettingsBegin, vdFlagCaptureSetSettingsEnd};*/ +/*\class VDC +VDE VDGetMaxSrcRect (short inputStd, Rect *maxSrcRect) +VDE VDGetActiveSrcRect(short inputStd, Rect *activeSrcRect) +VDE VD[GS]etDigitizerRect(Rect *digitizerRect) +VDE VDGetVBlankRect(short inputStd, Rect *vBlankRect) +VDE VDGetMaskPixMap(PixMapHandlemaskPixMap) +VDE VDGetPlayThruDestination(PixMapHandle * dest, Rect *destRect, MatrixRecord * m, RgnHandle *mask) +VDE VDUseThisCLUT(CTabHandle colorTableHandle) +VDE VD[SG*]etInputGammaValue(Fixed channel1, Fixed channel2, Fixed channel3) +VDE VD[GS]etBrightness(uint16 *) +VDE VD[GS]etContrast(uint16 *) +VDE VD[GS]etHue(uint16 *) +VDE VD[GS]etSharpness(uint16 *) +VDE VD[GS]etSaturation(uint16 *) +VDE VDGrabOneFrame(VDC ci) +VDE VDGetMaxAuxBuffer(PixMapHandle *pm, Rect *r) +VDE VDGetDigitizerInfo(DigitizerInfo *info) +VDE VDGetCurrentFlags(long *inputCurrentFlag, long *outputCurrentFlag) +VDE VD[SG*]etKeyColor(long index) +VDE VDAddKeyColor(long *index) +VDE VDGetNextKeyColor(long index) +VDE VD[GS]etKeyColorRange(RGBColor minRGB, RGBColor maxRGB) +VDE VDSetDigitizerUserInterrupt(long flags, VdigIntUPP userInterruptProc, long refcon) +VDE VD[SG*]etInputColorSpaceMode(short colorSpaceMode) +VDE VD[SG*]etClipState(short clipEnable) +VDE VDSetClipRgn(RgnHandle clipRegion) +VDE VDClearClipRgn(RgnHandle clipRegion) +VDE VDGetCLUTInUse(CTabHandle *colorTableHandle) +VDE VD[SG*]etPLLFilterType(short pllType) +VDE VDGetMaskandValue(uint16 blendLevel, long *mask, long *value) +VDE VDSetMasterBlendLevel(uint16 *blendLevel) +VDE VDSetPlayThruDestination(PixMapHandledest, RectPtr destRect, MatrixRecordPtr m, RgnHandle mask) +VDE VDSetPlayThruOnOff(short state) +VDE VD[SG*]etFieldPreference(short fieldFlag) +VDE VDPreflightDestination(Rect *digitizerRect, PixMap **dest, RectPtr destRect, MatrixRecordPtr m) +VDE VDPreflightGlobalRect(GrafPtr theWindow, Rect *globalRect) +VDE VDSetPlayThruGlobalRect(GrafPtr theWindow, Rect *globalRect) +VDE VDSetInputGammaRecord(VDGamRecPtrinputGammaPtr) +VDE VDGetInputGammaRecord(VDGamRecPtr *inputGammaPtr) +VDE VD[SG]etBlackLevelValue(uint16 *) +VDE VD[SG]etWhiteLevelValue(uint16 *) +VDE VDGetVideoDefaults(uint16 *blackLevel, uint16 *whiteLevel, uint16 *brightness, uint16 *hue, uint16 *saturation, uint16 *contrast, uint16 *sharpness) +VDE VDGetNumberOfInputs(short *inputs) +VDE VDGetInputFormat(short input, short *format) +VDE VD[SG*]etInput(short input) +VDE VDSetInputStandard(short inputStandard) +VDE VDSetupBuffers(VdigBufferRecListHandle bufferList) +VDE VDGrabOneFrameAsync(short buffer) +VDE VDDone(short buffer) +VDE VDSetCompression(OSTypecompressType, short depth, Rect *bounds, CodecQspatialQuality, CodecQtemporalQuality, long keyFrameRate) +VDE VDCompressOneFrameAsync(VDC ci) +VDE VDCompressDone(UInt8 *queuedFrameCount, Ptr *theData, long *dataSize, UInt8 *similarity, TimeRecord *t) +VDE VDReleaseCompressBuffer(Ptr bufferAddr) +VDE VDGetImageDescription(ImageDescriptionHandle desc) +VDE VDResetCompressSequence(VDC ci) +VDE VDSetCompressionOnOff(Boolean) +VDE VDGetCompressionTypes(VDCompressionListHandle h) +VDE VDSetTimeBase(TimeBase t) +VDE VDSetFrameRate(Fixed framesPerSecond) +VDE VDGetDataRate(long *milliSecPerFrame, Fixed *framesPerSecond, long *bytesPerSecond) +VDE VDGetSoundInputDriver(Str255 soundDriverName) +VDE VDGetDMADepths(long *depthArray, long *preferredDepth) +VDE VDGetPreferredTimeScale(TimeScale *preferred) +VDE VDReleaseAsyncBuffers(VDC ci) +VDE VDSetDataRate(long bytesPerSecond) +VDE VDGetTimeCode(TimeRecord *atTime, void *timeCodeFormat, void *timeCodeTime) +VDE VDUseSafeBuffers(Boolean useSafeBuffers) +VDE VDGetSoundInputSource(long videoInput, long *soundInput) +VDE VDGetCompressionTime(OSTypecompressionType, short depth, Rect *srcRect, CodecQ *spatialQuality, CodecQ *temporalQuality, ulong *compressTime) +VDE VDSetPreferredPacketSize(long preferredPacketSizeInBytes) +VDE VD[SG*]etPreferredImageDimensions(long width, long height) +VDE VDGetInputName(long videoInput, Str255 name) +VDE VDSetDestinationPort(CGrafPtr destPort) +VDE VDGetDeviceNameAndFlags(Str255 outName, UInt32 *outNameFlags) +VDE VDCaptureStateChanging(UInt32inStateFlags) +VDE VDGetUniqueIDs(UInt64 *outDeviceID, UInt64 *outInputID) +VDE VDSelectUniqueIDs(const UInt64 *inDeviceID, const UInt64 *inInputID) +\end class VDC +*/ + +\class FormatQuickTimeCamera < Format +struct FormatQuickTimeCamera : Format { + P<Dim> dim; + Pt<uint8> buf; + VDC vdc; + int m_newFrame; + SeqGrabComponent m_sg; + SGChannel m_vc; + short m_pixelDepth; + Rect rect; + GWorldPtr m_srcGWorld; + PixMapHandle m_pixMap; + Ptr m_baseAddr; + long m_rowBytes; + int m_quality; +//int m_colorspace; + FormatQuickTimeCamera() : vdc(0) {} + \decl void initialize (Symbol mode, Symbol source, String filename); + \decl void frame (); + \decl void close (); + \grin 0 int +}; + +// /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Components.h + +static int nn(int c) {return c?c:' ';} + +\def void initialize (Symbol mode, Symbol source, String filename) { + L +//vdc = SGGetVideoDigitizerComponent(c); + rb_call_super(argc,argv); + dim = new Dim(240,320,4); + OSErr e; + rect.top=rect.left=0; + rect.bottom=dim->v[0]; rect.right=dim->v[1]; + int n=0; + Component c = 0; + ComponentDescription cd; + cd.componentType = SeqGrabComponentType; + cd.componentSubType = 0; + cd.componentManufacturer = 0; + cd.componentFlags = 0; + cd.componentFlagsMask = 0; + for(;;) { + c = FindNextComponent(c, &cd); + if (!c) break; + ComponentDescription cd2; + Ptr name=0,info=0,icon=0; + GetComponentInfo(c,&cd2,&name,&info,&icon); + gfpost("Component #%d",n); + char *t = (char *)&cd.componentType; + gfpost(" type='%c%c%c%c'",nn(t[3]),nn(t[2]),nn(t[1]),nn(t[0])); + t = (char *)&cd.componentSubType; + gfpost(" subtype='%c%c%c%c'",nn(t[3]),nn(t[2]),nn(t[1]),nn(t[0])); + gfpost(" name=%08x, *name='%*s'",name, *name, name+1); + gfpost(" info=%08x, *info='%*s'",info, *name, info+1); + n++; + } + gfpost("number of components: %d",n); + m_sg = OpenDefaultComponent(SeqGrabComponentType, 0); + if(!m_sg) RAISE("could not open default component"); + e=SGInitialize(m_sg); + if(e!=noErr) RAISE("could not initialize SG"); + e=SGSetDataRef(m_sg, 0, 0, seqGrabDontMakeMovie); + if (e!=noErr) RAISE("dataref failed"); + e=SGNewChannel(m_sg, VideoMediaType, &m_vc); + if(e!=noErr) gfpost("could not make new SG channel"); + e=SGSetChannelBounds(m_vc, &rect); + if(e!=noErr) gfpost("could not set SG ChannelBounds"); + e=SGSetChannelUsage(m_vc, seqGrabPreview); + if(e!=noErr) gfpost("could not set SG ChannelUsage"); +// m_rowBytes = m_vidXSize*4; + switch (3) { + case 0: e=SGSetChannelPlayFlags(m_vc, channelPlayNormal); break; + case 1: e=SGSetChannelPlayFlags(m_vc, channelPlayHighQuality); break; + case 2: e=SGSetChannelPlayFlags(m_vc, channelPlayFast); break; + case 3: e=SGSetChannelPlayFlags(m_vc, channelPlayAllData); break; + } + int dataSize = dim->prod(); + buf = ARRAY_NEW(uint8,dataSize); + m_rowBytes = dim->prod(1); + e=QTNewGWorldFromPtr (&m_srcGWorld,k32ARGBPixelFormat, + &rect,NULL,NULL,0,buf,m_rowBytes); + if (0/*yuv*/) { + int dataSize = dim->prod()*2/4; + buf = ARRAY_NEW(uint8,dataSize); + m_rowBytes = dim->prod(1)*2/4; + e=QTNewGWorldFromPtr (&m_srcGWorld,k422YpCbCr8CodecType, + &rect,NULL,NULL,0,buf,m_rowBytes); + } + if (e!=noErr) RAISE("error #%d at QTNewGWorldFromPtr",e); + if (!m_srcGWorld) RAISE("Could not allocate off screen"); + SGSetGWorld(m_sg,(CGrafPtr)m_srcGWorld, NULL); + SGStartPreview(m_sg); +} + +/*pascal Boolean pix_videoDarwin :: SeqGrabberModalFilterProc (DialogPtr theDialog, const EventRecord *theEvent, short *itemHit, long refCon){ + Boolean handled = false; + if ((theEvent->what == updateEvt) && + ((WindowPtr) theEvent->message == (WindowPtr) refCon)) { + BeginUpdate ((WindowPtr) refCon); + EndUpdate ((WindowPtr) refCon); + handled = true; + } + WindowRef awin = GetDialogWindow(theDialog); + ShowWindow (awin); + SetWindowClass(awin,kUtilityWindowClass); + //ChangeWindowAttributes(awin,kWindowStandardHandlerAttribute,0); SGPanelEvent(m_sg,m_vc,theDialog,0,theEvent,itemHit,&handled); + // AEProcessAppleEvent (theEvent); + return handled; +} +void pix_videoDarwin :: DoVideoSettings(){ + Rect newActiveVideoRect; + Rect curBounds, curVideoRect, newVideoRect; + ComponentResult err; + SGModalFilterUPP seqGragModalFilterUPP; + err = SGGetChannelBounds (m_vc, &curBounds); + err = SGGetVideoRect (m_vc, &curVideoRect); + err = SGPause (m_sg, true); + seqGragModalFilterUPP = (SGModalFilterUPP)NewSGModalFilterUPP(SeqGrabberModalFilterProc); + err = SGSettingsDialog(m_sg, m_vc, 0, + NULL, seqGrabSettingsPreviewOnly, seqGragModalFilterUPP, (long)m_srcGWorld); + DisposeSGModalFilterUPP(seqGragModalFilterUPP); + err = SGGetVideoRect (m_vc, &newVideoRect); + err = SGGetSrcVideoBounds (m_vc, &newActiveVideoRect); + err = SGPause (m_sg, false); +} +*/ + +\def void frame () { + GridOutlet out(this,0,dim); + out.send(dim->prod(),buf); +L} + +\def void close () { + L + if (m_vc) { + if (::SGDisposeChannel(m_sg, m_vc)) RAISE("SGDisposeChannel"); + m_vc=0; + } + if (m_sg) { + if (::CloseComponent(m_sg)) RAISE("CloseComponent"); + m_sg = NULL; + if (m_srcGWorld) { + ::DisposeGWorld(m_srcGWorld); + m_pixMap = NULL; + m_srcGWorld = NULL; + m_baseAddr = NULL; + } + } +} + +GRID_INLET(FormatQuickTimeCamera,0) { + RAISE("Unimplemented. Sorry."); +//!@#$ + if (in->dim->n != 3) + RAISE("expecting 3 dimensions: rows,columns,channels"); + if (in->dim->get(2) != 3) + RAISE("expecting 3 channels (got %d)",in->dim->get(2)); + in->set_factor(in->dim->prod()); +} GRID_FLOW { +} GRID_FINISH { +} GRID_END + +\classinfo { + IEVAL(rself, +\ruby + install '#io:quicktimecamera',1,1 + @comment="Apple Quicktime (CAMERA MODULE)" + @flags=4 +\end ruby +);} +\end class FormatQuickTimeCamera + +\class FormatQuickTimeApple < Format +struct FormatQuickTimeApple : Format { + Movie movie; + TimeValue time; + short movie_file; + GWorldPtr gw; /* just like an X11 Image or Pixmap, maybe. */ + Pt<uint8> buffer; + P<Dim> dim; + int nframe, nframes; + + FormatQuickTimeApple() : movie(0), time(0), movie_file(0), gw(0), + buffer(), dim(0), nframe(0), nframes(0) {} + \decl void initialize (Symbol mode, Symbol source, String filename); + \decl void close (); + \decl void codec_m (String c); + \decl void colorspace_m (Symbol c); + \decl Ruby frame (); + \decl void seek (int frame); + \grin 0 +}; + +\def void seek (int frame) { + nframe=frame; +} + +\def Ruby frame () { + CGrafPtr savedPort; + GDHandle savedDevice; + SetMovieGWorld(movie,gw,GetGWorldDevice(gw)); + Rect r; + GetMovieBox(movie,&r); + PixMapHandle pixmap = GetGWorldPixMap(gw); + short flags = nextTimeStep; + if (nframe>=nframes) return Qfalse; + if (nframe==0) flags |= nextTimeEdgeOK; + TimeValue duration; + OSType mediaType = VisualMediaCharacteristic; + GetMovieNextInterestingTime(movie, + flags,1,&mediaType,time,0,&time,&duration); + if (time<0) { + time=0; + return Qfalse; + } +// gfpost("quicktime frame #%d; time=%d duration=%d", nframe, (long)time, (long)duration); + SetMovieTimeValue(movie,nframe*duration); + MoviesTask(movie,0); + GridOutlet out(this,0,dim); + Pt<uint32> bufu32 = Pt<uint32>((uint32 *)buffer.p,dim->prod()/4); + int n = dim->prod()/4; + int i; + for (i=0; i<n&-4; i+=4) { + bufu32[i+0]=(bufu32[i+0]<<8)+(bufu32[i+0]>>24); + bufu32[i+1]=(bufu32[i+1]<<8)+(bufu32[i+1]>>24); + bufu32[i+2]=(bufu32[i+2]<<8)+(bufu32[i+2]>>24); + bufu32[i+3]=(bufu32[i+3]<<8)+(bufu32[i+3]>>24); + } + for (; i<n; i++) { + bufu32[i+0]=(bufu32[i+0]<<8)+(bufu32[i+0]>>24); + } + + out.send(dim->prod(),buffer); + int nf=nframe; + nframe++; + return INT2NUM(nf); +} + +GRID_INLET(FormatQuickTimeApple,0) { + RAISE("Unimplemented. Sorry."); +//!@#$ + if (in->dim->n != 3) + RAISE("expecting 3 dimensions: rows,columns,channels"); + if (in->dim->get(2) != 3) + RAISE("expecting 3 channels (got %d)",in->dim->get(2)); + in->set_factor(in->dim->prod()); +} GRID_FLOW { +} GRID_FINISH { +} GRID_END + +\def void codec_m (String c) { RAISE("Unimplemented. Sorry."); } +\def void colorspace_m (Symbol c) { RAISE("Unimplemented. Sorry."); } + +\def void close () { +//!@#$ + if (movie) { + DisposeMovie(movie); + DisposeGWorld(gw); + CloseMovieFile(movie_file); + movie_file=0; + } + rb_call_super(argc,argv); +} + +\def void initialize (Symbol mode, Symbol source, String filename) { + int err; + rb_call_super(argc,argv); + if (source==SYM(file)) { + filename = rb_funcall(mGridFlow,SI(find_file),1,filename); + FSSpec fss; + FSRef fsr; + err = FSPathMakeRef((const UInt8 *)rb_str_ptr(filename), &fsr, NULL); + if (err) goto err; + err = FSGetCatalogInfo(&fsr, kFSCatInfoNone, NULL, NULL, &fss, NULL); + if (err) goto err; + err = OpenMovieFile(&fss,&movie_file,fsRdPerm); + if (err) goto err; + } else { + RAISE("usage: quicktime [file <filename> | camera bleh]"); + } + NewMovieFromFile(&movie, movie_file, NULL, NULL, newMovieActive, NULL); + Rect r; + GetMovieBox(movie, &r); + gfpost("handle=%d movie=%d tracks=%d", + movie_file, movie, GetMovieTrackCount(movie)); + gfpost("duration=%d; timescale=%d cHz", + (long)GetMovieDuration(movie), + (long)GetMovieTimeScale(movie)); + nframes = GetMovieDuration(movie); /* i don't think so */ + gfpost("rect=((%d..%d),(%d..%d))", + r.top, r.bottom, r.left, r.right); + OffsetRect(&r, -r.left, -r.top); + SetMovieBox(movie, &r); + dim = new Dim(r.bottom-r.top, r.right-r.left, 4); + SetMoviePlayHints(movie, hintsHighQuality, hintsHighQuality); + buffer = ARRAY_NEW(uint8,dim->prod()); + err = QTNewGWorldFromPtr(&gw, k32ARGBPixelFormat, &r, + NULL, NULL, 0, buffer, dim->prod(1)); + if (err) goto err; + return; +err: + RAISE("can't open file `%s': error #%d (%s)", rb_str_ptr(filename), + err, + rb_str_ptr(rb_funcall(mGridFlow,SI(macerr),1,INT2NUM(err)))); +} + +\classinfo { + EnterMovies(); +IEVAL(rself, +\ruby + install '#io:quicktime',1,1 + @comment="Apple Quicktime (using Apple's)" + @flags=4 + suffixes_are'mov' + def self.new(mode,source,filename) + if source==:camera then FormatQuickTimeCamera.new(mode,source,filename) else super end + end +\end ruby +);} +\end class FormatQuickTimeApple +void startup_quicktimeapple () { + \startall +} diff --git a/externals/gridflow/format/quicktimehw.c b/externals/gridflow/format/quicktimehw.c new file mode 100644 index 00000000..4ae77209 --- /dev/null +++ b/externals/gridflow/format/quicktimehw.c @@ -0,0 +1,244 @@ +/* + $Id: quicktimehw.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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. +*/ + +#include "../base/grid.h.fcs" +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <quicktime/quicktime.h> +#include <quicktime/colormodels.h> + +#include <quicktime/lqt_version.h> +#ifdef LQT_VERSION +#include <quicktime/lqt.h> +#include <quicktime/lqt_codecinfo.h> +#endif + +\class FormatQuickTimeHW < Format +struct FormatQuickTimeHW : Format { + quicktime_t *anim; + int track; + P<Dim> dim; + char *codec; + int colorspace; + int channels; + bool started; + P<Dim> force; + int length; // in frames + float64 framerate; + P<BitPacking> bit_packing; + FormatQuickTimeHW() : track(0), dim(0), codec(QUICKTIME_RAW), + started(false), force(0), framerate(29.97), bit_packing(0) {} + \decl void initialize (Symbol mode, Symbol source, String filename); + \decl void close (); + \decl Ruby frame (); + \decl void seek (int frame); + + \decl void _0_force_size (int32 height, int32 width); + \decl void _0_codec (String c); + \decl void _0_colorspace (Symbol c); + \decl void _0_parameter (Symbol name, int32 value); + \decl void _0_framerate (float64 f); + \decl void _0_size (int32 height, int32 width); + \grin 0 int +}; + +\def void _0_force_size (int32 height, int32 width) { force = new Dim(height, width); } +\def void seek (int frame) {quicktime_set_video_position(anim,frame,track);} + +\def Ruby frame () { + int nframe = quicktime_video_position(anim,track); + int length2 = quicktime_video_length(anim,track); + if (nframe >= length) { +// gfpost("nframe=%d length=%d length2=%d",nframe,length,length2); + return Qfalse; + } + /* if it works, only do it once, to avoid silly stderr messages forgotten in LQT */ + if (!quicktime_reads_cmodel(anim,colorspace,0) && !started) { + RAISE("LQT says this video cannot be decoded into the chosen colorspace"); + } + int sx = quicktime_video_width(anim,track); + int sy = quicktime_video_height(anim,track); + int sz = quicktime_video_depth(anim,track); + channels = sz/8; // hack. how do i get the video's native colormodel ? + switch (sz) { + case 24: colorspace=BC_RGB888; break; + case 32: colorspace=BC_RGBA8888; break; + default: gfpost("strange quicktime. ask matju."); break; + } + if (force) { + sy = force->get(0); + sx = force->get(1); + } + Pt<uint8> buf = ARRAY_NEW(uint8,sy*sx*channels); + uint8 *rows[sy]; for (int i=0; i<sy; i++) rows[i]=buf+i*sx*channels; + int result = quicktime_decode_scaled(anim,0,0,sx,sy,sx,sy,colorspace,rows,track); + GridOutlet out(this,0,new Dim(sy, sx, channels), + NumberTypeE_find(rb_ivar_get(rself,SI(@cast)))); + int bs = out.dim->prod(1); + out.give(sy*sx*channels,buf); + started=true; + return INT2NUM(nframe); +} + +//!@#$ should also support symbol values (how?) +\def void _0_parameter (Symbol name, int32 value) { + quicktime_set_parameter(anim, (char*)rb_sym_name(name), &value); +} + +\def void _0_framerate (float64 f) { + framerate=f; + quicktime_set_framerate(anim, f); +} + +\def void _0_size (int32 height, int32 width) { + if (dim) RAISE("video size already set!"); + // first frame: have to do setup + dim = new Dim(height, width, 3); + quicktime_set_video(anim,1,dim->get(1),dim->get(0),framerate,codec); + quicktime_set_cmodel(anim,colorspace); +} + +GRID_INLET(FormatQuickTimeHW,0) { + if (in->dim->n != 3) RAISE("expecting 3 dimensions: rows,columns,channels"); + if (in->dim->get(2)!=channels) RAISE("expecting %d channels (got %d)",channels,in->dim->get(2)); + in->set_factor(in->dim->prod()); + if (dim) { + if (!dim->equal(in->dim)) RAISE("all frames should be same size"); + } else { + // first frame: have to do setup + dim = in->dim; + quicktime_set_video(anim,1,dim->get(1),dim->get(0),framerate,codec); + quicktime_set_cmodel(anim,colorspace); + quicktime_set_depth(anim,8*channels,track); + } +} GRID_FLOW { + int sx = quicktime_video_width(anim,track); + int sy = quicktime_video_height(anim,track); + uint8 *rows[sy]; + if (sizeof(T)>1) { + uint8 data2[n]; + bit_packing->pack(sx*sy,data,Pt<uint8>(data2,n)); + for (int i=0; i<sy; i++) rows[i]=data2+i*sx*channels; + quicktime_encode_video(anim,rows,track); + } else { + for (int i=0; i<sy; i++) rows[i]=data+i*sx*channels; + quicktime_encode_video(anim,rows,track); + } +} GRID_FINISH { +} GRID_END + +\def void _0_codec (String c) { + //fprintf(stderr,"codec = %s\n",rb_str_ptr(rb_inspect(c))); +#ifdef LQT_VERSION + char buf[5]; + strncpy(buf,rb_str_ptr(c),4); + for (int i=rb_str_len(c); i<4; i++) buf[i]=' '; + buf[4]=0; + Ruby fourccs = rb_ivar_get(rb_obj_class(rself),SI(@fourccs)); + if (Qnil==rb_hash_aref(fourccs,rb_str_new2(buf))) + RAISE("warning: unknown fourcc '%s' (%s)", + buf, rb_str_ptr(rb_inspect(rb_funcall(fourccs,SI(keys),0)))); +#endif + codec = strdup(buf); +} + +\def void _0_colorspace (Symbol c) { + if (0) { + } else if (c==SYM(rgb)) { channels=3; colorspace=BC_RGB888; + } else if (c==SYM(rgba)) { channels=4; colorspace=BC_RGBA8888; + } else if (c==SYM(bgr)) { channels=3; colorspace=BC_BGR888; + } else if (c==SYM(bgrn)) { channels=4; colorspace=BC_BGR8888; + } else if (c==SYM(yuv)) { channels=3; colorspace=BC_YUV888; + } else if (c==SYM(yuva)) { channels=4; colorspace=BC_YUVA8888; + } else if (c==SYM(YUV420P)) { channels=3; colorspace=BC_YUV420P; + } else RAISE("unknown colorspace '%s' (supported: rgb, rgba, bgr, bgrn, yuv, yuva)",rb_sym_name(c)); +} + +\def void close () { + if (anim) { quicktime_close(anim); anim=0; } + rb_call_super(argc,argv); +} + +// libquicktime may be nice, but it won't take a filehandle, only filename +\def void initialize (Symbol mode, Symbol source, String filename) { + rb_call_super(argc,argv); + if (source!=SYM(file)) RAISE("usage: quicktime file <filename>"); + filename = rb_funcall(mGridFlow,SI(find_file),1,filename); + anim = quicktime_open(rb_str_ptr(filename),mode==SYM(in),mode==SYM(out)); + if (!anim) RAISE("can't open file `%s': %s", rb_str_ptr(filename), strerror(errno)); + if (mode==SYM(in)) { + length = quicktime_video_length(anim,track); + gfpost("quicktime: codec=%s height=%d width=%d depth=%d framerate=%f", + quicktime_video_compressor(anim,track), + quicktime_video_height(anim,track), + quicktime_video_width(anim,track), + quicktime_video_depth(anim,track), + quicktime_frame_rate(anim,track)); +/* This doesn't really work: (is it just for encoding?) + if (!quicktime_supported_video(anim,track)) + RAISE("quicktime: unsupported codec: %s", + quicktime_video_compressor(anim,track)); +*/ + } + _0_colorspace(0,0,SYM(rgb)); + quicktime_set_cpus(anim,1); + uint32 mask[3] = {0x0000ff,0x00ff00,0xff0000}; + bit_packing = new BitPacking(is_le(),3,3,mask); +} + +\classinfo { + IEVAL(rself, +\ruby + install '#io:quicktime',1,1 + @comment=%[Burkhard Plaum's (or HeroineWarrior's) libquicktime] + suffixes_are 'mov' + @flags=6 + def self.info; %[codecs: #{@codecs.keys.join' '}] end +\end ruby +); + +#ifdef LQT_VERSION + lqt_registry_init(); + int n = lqt_get_num_video_codecs(); + Ruby codecs = rb_hash_new(); + Ruby fourccs = rb_hash_new(); + for (int i=0; i<n; i++) { + const lqt_codec_info_t *s = lqt_get_video_codec_info(i); + Ruby name = rb_str_new2(s->name); + Ruby f = rb_ary_new2(s->num_fourccs); + for (int j=0; j<s->num_fourccs; j++) { + Ruby fn = rb_str_new2(s->fourccs[j]); + rb_ary_push(f,fn); + rb_hash_aset(fourccs,fn,name); + } + rb_hash_aset(codecs,name,f); + } + rb_ivar_set(rself,SI(@codecs),codecs); + rb_ivar_set(rself,SI(@fourccs),fourccs); +#endif +} +\end class FormatQuickTimeHW +void startup_quicktimehw () { + \startall +} diff --git a/externals/gridflow/format/sdl.c b/externals/gridflow/format/sdl.c new file mode 100644 index 00000000..628f71ee --- /dev/null +++ b/externals/gridflow/format/sdl.c @@ -0,0 +1,123 @@ +/* + $Id: sdl.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include "../base/grid.h.fcs" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/time.h> +#include <signal.h> +#include <SDL/SDL.h> + +static bool in_use = false; + +\class FormatSDL < Format +struct FormatSDL : Format { + SDL_Surface *screen; + P<BitPacking> bit_packing; + P<Dim> dim; + void resize_window (int sx, int sy); + void call (); + Pt<uint8> pixels () { + return Pt<uint8>((uint8 *)screen->pixels, + dim->prod(0,1)*bit_packing->bytes); + } + \decl void initialize (Symbol mode); + \decl void close (); + \grin 0 int +}; + +void FormatSDL::call() { + SDL_Event event; + while(SDL_PollEvent(&event)) {} + IEVAL(rself,"@clock.delay 20"); +} + +void FormatSDL::resize_window (int sx, int sy) { + dim = new Dim(sy,sx,3); + screen = SDL_SetVideoMode(sx,sy,0,SDL_SWSURFACE); + if (!screen) + RAISE("Can't switch to (%d,%d,%dbpp): %s", sy,sx,24, SDL_GetError()); +} + +GRID_INLET(FormatSDL,0) { + if (in->dim->n != 3) + RAISE("expecting 3 dimensions: rows,columns,channels"); + if (in->dim->get(2) != 3) + RAISE("expecting 3 channels: red,green,blue (got %d)",in->dim->get(2)); + int sxc = in->dim->prod(1); + int sx = in->dim->get(1), osx = dim->get(1); + int sy = in->dim->get(0), osy = dim->get(0); + in->set_factor(sxc); + if (sx!=osx || sy!=osy) resize_window(sx,sy); +} GRID_FLOW { + int bypl = screen->pitch; + int sxc = in->dim->prod(1); + int sx = in->dim->get(1); + int y = in->dex / sxc; + assert((in->dex % sxc) == 0); + assert((n % sxc) == 0); + if (SDL_MUSTLOCK(screen)) if (SDL_LockSurface(screen) < 0) return; //??? + for (; n>0; y++, data+=sxc, n-=sxc) { + /* convert line */ + bit_packing->pack(sx, data, pixels()+y*bypl); + } + if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); +} GRID_FINISH { + SDL_UpdateRect(screen,0,0,in->dim->get(1),in->dim->get(0)); +} GRID_END + +\def void close () { + IEVAL(rself,"@clock.unset"); + in_use=false; +} + +\def void initialize (Symbol mode) { + dim=0;screen=0; + rb_call_super(argc,argv); + if (in_use) RAISE("only one FormatSDL object at a time; sorry"); + in_use=true; + if (SDL_Init(SDL_INIT_VIDEO)<0) + RAISE("SDL_Init() error: %s",SDL_GetError()); + atexit(SDL_Quit); + resize_window(320,240); + SDL_PixelFormat *f = screen->format; + uint32 mask[3] = {f->Rmask,f->Gmask,f->Bmask}; + switch (f->BytesPerPixel) { + case 1: RAISE("8 bpp not supported"); break; + case 2: case 3: case 4: + bit_packing = new BitPacking(is_le(),f->BytesPerPixel,3,mask); + break; + default: RAISE("%d bytes/pixel: how do I deal with that?",f->BytesPerPixel); break; + } + IEVAL(rself,"@clock = Clock.new self"); +} + +\classinfo { + IEVAL(rself,"install '#io:sdl',1,1;@flags=2;@comment='Simple Directmedia Layer'"); +} +\end class FormatSDL +void startup_sdl () { + \startall +} diff --git a/externals/gridflow/format/videodev.c b/externals/gridflow/format/videodev.c new file mode 100644 index 00000000..e907eaa9 --- /dev/null +++ b/externals/gridflow/format/videodev.c @@ -0,0 +1,544 @@ +/* + $Id: videodev.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003,2004 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. +*/ + +#include "../base/grid.h.fcs" +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/videodev.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <sys/mman.h> + +/* **************************************************************** */ + +typedef video_capability VideoCapability; +typedef video_channel VideoChannel ; +typedef video_tuner VideoTuner ; +typedef video_window VideoWindow ; +typedef video_picture VideoPicture ; +typedef video_mbuf VideoMbuf ; +typedef video_mmap VideoMmap ; + +static const char *ruby_code = +\ruby +flags :name,:VideoTypeFlags,:start,0,:values,%w( + CAPTURE TUNER TELETEXT OVERLAY CHROMAKEY CLIPPING FRAMERAM + SCALES MONOCHROME SUBCAPTURE + MPEG_DECODER MPEG_ENCODER MJPEG_DECODER MJPEG_ENCODER +) +flags :name,:TunerFlags,:start,0,:values,%w( + PAL NTSC SECAM LOW NORM DUMMY5 DUMMY6 STEREO_ON RDS_ON MBS_ON +) +flags :name,:ChannelFlags,:start,0,:values,%w( + TUNER AUDIO NORM +) +choice :name,:VideoPaletteChoice,:start,0,:values,%w( + NIL GREY HI240 + RGB565 RGB24 RGB32 RGB555 + YUV422 YUYV UYVY YUV420 YUV411 RAW + YUV422P YUV411P YUV420P YUV410P +) +choice :name,:VideoModeChoice,:start,0,:values,%w( + PAL NTSC SECAM AUTO +) +\end ruby +; + +/* **************************************************************** */ + +/* +#define WH(_field_,_spec_) \ + sprintf(buf+strlen(buf), "%s: " _spec_ "; ", #_field_, self->_field_); +#define WHYX(_name_,_fieldy_,_fieldx_) \ + sprintf(buf+strlen(buf), "%s: y=%d, x=%d; ", #_name_, self->_fieldy_, self->_fieldx_); +#define WHFLAGS(_field_,_table_) { \ + char *foo; \ + sprintf(buf+strlen(buf), "%s: %s; ", #_field_, \ + foo=flags_to_s(self->_field_,COUNT(_table_),_table_)); \ + delete[] foo;} +#define WHCHOICE(_field_,_table_) { \ + char *foo; \ + sprintf(buf+strlen(buf), "%s: %s; ", #_field_, \ + foo=choice_to_s(self->_field_,COUNT(_table_),_table_));\ + delete[] foo;} +static char *flags_to_s(int value, int n, named_int *table) { + char foo[256]; + *foo = 0; + for(int i=0; i<n; i++) { + if ((value & (1<<i)) == 0) continue; + if (*foo) strcat(foo," | "); + strcat(foo,table[i].name); + } + if (!*foo) strcat(foo,"0"); + return strdup(foo); +} +static char *choice_to_s(int value, int n, named_int *table) { + if (value < 0 || value >= n) { + char foo[64]; + sprintf(foo,"(Unknown #%d)",value); + return strdup(foo); + } else { + return strdup(table[value].name); + } +} +*/ + +class RStream { +public: + Ruby a; + RStream(Ruby a) : a(a) {} + RStream &operator <<(/*Ruby*/ void *v) { rb_ary_push(a,(Ruby)v); return *this; } + RStream &operator <<(int v) { return *this<<(void *)INT2NUM(v); } +}; + +static void gfpost(VideoChannel *self) { + RStream rs(rb_ary_new()); + rs << (void *)SYM(VideoChannel) << self->channel + << (void *)rb_str_new(self->name,strnlen(self->name,32)) + << self->tuners << self->flags << self->type << self->norm; + rb_p(rs.a); +} + +static void gfpost(VideoTuner *self) { + RStream rs(rb_ary_new()); + rs << (void *)SYM(VideoTuner) << self->tuner + << (void *)rb_str_new(self->name,strnlen(self->name,32)) + << self->rangelow << self->rangehigh + << self->flags << self->mode << self->signal; + rb_p(rs.a); +} + +static void gfpost(VideoCapability *self) { + RStream rs(rb_ary_new()); + rs << (void *)SYM(VideoCapability) + << (void *)rb_str_new(self->name,strnlen(self->name,32)) + << self->type + << self->channels << self->audios + << self->maxheight << self->maxwidth + << self->minheight << self->minwidth; + rb_p(rs.a); +} + +static void gfpost(VideoWindow *self) { + RStream rs(rb_ary_new()); + rs << (void *)SYM(VideoWindow) + << self->y << self->x + << self->height << self->width + << self->chromakey << self->flags << self->clipcount; + rb_p(rs.a); +} + +static void gfpost(VideoPicture *self) { + RStream rs(rb_ary_new()); + rs << (void *)SYM(VideoPicture) + << self->brightness << self->contrast << self->colour + << self->hue << self->whiteness << self->depth << self->palette; + rb_p(rs.a); +} + +static void gfpost(VideoMbuf *self) { + RStream rs(rb_ary_new()); + rs << (void *)SYM(VideoMBuf) << self->size << self->frames; + for (int i=0; i<4; i++) rs << self->offsets[i]; + rb_p(rs.a); +} + +static void gfpost(VideoMmap *self) { + RStream rs(rb_ary_new()); + rs << (void *)SYM(VideoMMap) << self->frame + << self->height << self->width << self->format; + rb_p(rs.a); +}; + +/* **************************************************************** */ + +\class FormatVideoDev < Format +struct FormatVideoDev : Format { + VideoCapability vcaps; + VideoMbuf vmbuf; + VideoMmap vmmap; + Pt<uint8> image; + int palette; + int queue[8], queuesize, queuemax, next_frame; + int current_channel, current_tuner; + bool use_mmap; + P<BitPacking> bit_packing; + P<Dim> dim; + + FormatVideoDev () : queuesize(0), queuemax(2), next_frame(0), use_mmap(true), bit_packing(0), dim(0) {} + void frame_finished (Pt<uint8> buf); + + \decl void initialize (Symbol mode, String filename, Symbol option=Qnil); + \decl void initialize2 (); + \decl void close (); + \decl void alloc_image (); + \decl void dealloc_image (); + \decl void frame (); + \decl void frame_ask (); + \grin 0 int + + \decl void _0_size (int sy, int sx); + \decl void _0_norm (int value); + \decl void _0_tuner (int value); + \decl void _0_channel (int value); + \decl void _0_frequency (int value); + \decl void _0_transfer (Symbol sym, int queuemax=2); + \decl void _0_colorspace (Symbol c); + \decl void _0_get (Symbol attr=0); + \decl void _0_brightness (uint16 value); + \decl void _0_hue (uint16 value); + \decl void _0_colour (uint16 value); + \decl void _0_contrast (uint16 value); + \decl void _0_whiteness (uint16 value); +}; + +#define DEBUG(args...) 42 +//#define DEBUG(args...) gfpost + +#define IOCTL(_f_,_name_,_arg_) \ + (DEBUG("fd%d.ioctl(0x%08x(:%s),0x%08x)\n",_f_,_name_,#_name_,_arg_), \ + ioctl(_f_,_name_,_arg_)) + +#define WIOCTL(_f_,_name_,_arg_) \ + (DEBUG("fd%d.ioctl(0x%08x(:%s),0x%08x)\n",_f_,_name_,#_name_,_arg_), \ + ioctl(_f_,_name_,_arg_) < 0) && \ + (gfpost("ioctl %s: %s",#_name_,strerror(errno)),1) + +#define WIOCTL2(_f_,_name_,_arg_) \ + ((DEBUG("fd%d.ioctl(0x%08x(:%s),0x%08x)\n",_f_,_name_,#_name_,_arg_), \ + ioctl(_f_,_name_,_arg_) < 0) && \ + (gfpost("ioctl %s: %s",#_name_,strerror(errno)), \ + RAISE("ioctl error"), 0)) + +#define GETFD NUM2INT(rb_funcall(rb_ivar_get(rself,SI(@stream)),SI(fileno),0)) + +\def void _0_size (int sy, int sx) { + int fd = GETFD; + VideoWindow grab_win; + // !@#$ bug here: won't flush the frame queue + dim = new Dim(sy,sx,3); + WIOCTL(fd, VIDIOCGWIN, &grab_win); + gfpost(&grab_win); + grab_win.clipcount = 0; + grab_win.flags = 0; + if (sy && sx) { + grab_win.height = sy; + grab_win.width = sx; + } + gfpost(&grab_win); + WIOCTL(fd, VIDIOCSWIN, &grab_win); + WIOCTL(fd, VIDIOCGWIN, &grab_win); + gfpost(&grab_win); +} + +\def void dealloc_image () { + if (!image) return; + if (!use_mmap) { + delete[] (uint8 *)image; + } else { + munmap(image, vmbuf.size); + image = Pt<uint8>(); + } +} + +\def void alloc_image () { + if (!use_mmap) { + image = ARRAY_NEW(uint8,dim->prod(0,1)*bit_packing->bytes); + return; + } + int fd = GETFD; + WIOCTL2(fd, VIDIOCGMBUF, &vmbuf); + image = Pt<uint8>((uint8 *) + mmap(0,vmbuf.size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0), + vmbuf.size); + if (((int)image)<=0) { + image=Pt<uint8>(); + RAISE("mmap: %s", strerror(errno)); + } +} + +\def void frame_ask () { + int fd = GETFD; + if (queuesize>=queuemax) RAISE("queue is full (queuemax=%d)",queuemax); + if (queuesize>=vmbuf.frames) RAISE("queue is full (vmbuf.frames=%d)",vmbuf.frames); + vmmap.frame = queue[queuesize++] = next_frame; + vmmap.format = palette; + vmmap.width = dim->get(1); + vmmap.height = dim->get(0); + WIOCTL2(fd, VIDIOCMCAPTURE, &vmmap); + next_frame = (next_frame+1) % vmbuf.frames; +} + +void FormatVideoDev::frame_finished (Pt<uint8> buf) { + GridOutlet out(this,0,dim,NumberTypeE_find(rb_ivar_get(rself,SI(@cast)))); + /* picture is converted here. */ + int sy = dim->get(0); + int sx = dim->get(1); + int bs = dim->prod(1); + if (palette==VIDEO_PALETTE_YUV420P) { + STACK_ARRAY(uint8,b2,bs); + for(int y=0; y<sy; y++) { + Pt<uint8> bufy = buf+sx*y; + Pt<uint8> bufu = buf+sx*sy +(sx/2)*(y/2); + Pt<uint8> bufv = buf+sx*sy*5/4+(sx/2)*(y/2); + for (int x=0; x<sx; x++) { + b2[x*3+0]=bufy[x]; + b2[x*3+1]=bufu[x/2]; + b2[x*3+2]=bufv[x/2]; + } + out.send(bs,b2); + } + } else if (bit_packing) { + STACK_ARRAY(uint8,b2,bs); + for(int y=0; y<sy; y++) { + Pt<uint8> buf2 = buf+bit_packing->bytes*sx*y; + bit_packing->unpack(sx,buf2,b2); + out.send(bs,b2); + } + } else { + out.send(sy*bs,buf); + } +} + +static int read2(int fd, uint8 *image, int n) { + int r=0; + for (; n>0; ) { + int rr=read(fd,image,n); + if (rr<0) return rr; + r+=rr, image+=rr, n-=rr; + } + return r; +} + +static int read3(int fd, uint8 *image, int n) { + int r=read(fd,image,n); + if (r<0) return r; + return n; +} + +\def void frame () { + if (!image) rb_funcall(rself,SI(alloc_image),0); + int fd = GETFD; + if (!use_mmap) { + /* picture is read at once by frame() to facilitate debugging. */ + int tot = dim->prod(0,1) * bit_packing->bytes; + int n = (int) read3(fd,image,tot); + if (n==tot) frame_finished(image); + if (0> n) RAISE("error reading: %s", strerror(errno)); + if (n < tot) RAISE("unexpectedly short picture: %d of %d",n,tot); + return; + } + while(queuesize<queuemax) rb_funcall(rself,SI(frame_ask),0); + vmmap.frame = queue[0]; + uint64 t0 = gf_timeofday(); + WIOCTL2(fd, VIDIOCSYNC, &vmmap); + uint64 t1 = gf_timeofday(); + //if (t1-t0 > 100) gfpost("VIDIOCSYNC delay: %d us",t1-t0); + frame_finished(image+vmbuf.offsets[queue[0]]); + queuesize--; + for (int i=0; i<queuesize; i++) queue[i]=queue[i+1]; + rb_funcall(rself,SI(frame_ask),0); +} + +GRID_INLET(FormatVideoDev,0) { + RAISE("can't write."); +} GRID_FLOW { +} GRID_FINISH { +} GRID_END + +\def void _0_norm (int value) { + int fd = GETFD; + VideoTuner vtuner; + vtuner.tuner = current_tuner; + if (value<0 || value>3) RAISE("norm must be in range 0..3"); + if (0> IOCTL(fd, VIDIOCGTUNER, &vtuner)) { + gfpost("no tuner #%d", value); + } else { + vtuner.mode = value; + gfpost(&vtuner); + WIOCTL(fd, VIDIOCSTUNER, &vtuner); + } +} + +\def void _0_tuner (int value) { + int fd = GETFD; + VideoTuner vtuner; + vtuner.tuner = current_tuner = value; + if (0> IOCTL(fd, VIDIOCGTUNER, &vtuner)) RAISE("no tuner #%d", value); + vtuner.mode = VIDEO_MODE_NTSC; //??? + gfpost(&vtuner); + WIOCTL(fd, VIDIOCSTUNER, &vtuner); +} + +\def void _0_channel (int value) { + int fd = GETFD; + VideoChannel vchan; + vchan.channel = value; + current_channel = value; + if (0> IOCTL(fd, VIDIOCGCHAN, &vchan)) RAISE("no channel #%d", value); + gfpost(&vchan); + WIOCTL(fd, VIDIOCSCHAN, &vchan); + if (vcaps.type & VID_TYPE_TUNER) rb_funcall(rself,SI(_0_tuner),1,INT2NUM(0)); +} + +\def void _0_frequency (int value) { + int fd = GETFD; + if (0> IOCTL(fd, VIDIOCSFREQ, &value)) RAISE("can't set frequency to %d",value); +} + +\def void _0_transfer (Symbol sym, int queuemax=2) { + if (sym == SYM(read)) { + rb_funcall(rself,SI(dealloc_image),0); + use_mmap = false; + gfpost("transfer read"); + } else if (sym == SYM(mmap)) { + rb_funcall(rself,SI(dealloc_image),0); + use_mmap = true; + rb_funcall(rself,SI(alloc_image),0); + queuemax=min(queuemax,vmbuf.frames); + gfpost("transfer mmap with queuemax=%d (max max is vmbuf.frames=%d)" + ,queuemax,vmbuf.frames); + this->queuemax=queuemax; + } else RAISE("don't know that transfer mode"); +} + +#define PICTURE_ATTR(_name_) {\ + int fd = GETFD; \ + VideoPicture vp; \ + WIOCTL(fd, VIDIOCGPICT, &vp); \ + vp._name_ = value; \ + WIOCTL(fd, VIDIOCSPICT, &vp);} + +\def void _0_brightness (uint16 value) {PICTURE_ATTR(brightness)} +\def void _0_hue (uint16 value) {PICTURE_ATTR(hue)} +\def void _0_colour (uint16 value) {PICTURE_ATTR(colour)} +\def void _0_contrast (uint16 value) {PICTURE_ATTR(contrast)} +\def void _0_whiteness (uint16 value) {PICTURE_ATTR(whiteness)} + +#define PICTURE_ATTR_GET(_name_) { \ + int fd = GETFD; \ + VideoPicture vp; \ + WIOCTL(fd, VIDIOCGPICT, &vp); \ + Ruby argv[3] = {INT2NUM(1), SYM(_name_), INT2NUM(vp._name_)}; \ + send_out(COUNT(argv),argv);} + +\def void _0_get (Symbol attr) { + if (!attr) { + _0_get(0,0,SYM(brightness)); + _0_get(0,0,SYM(hue )); + _0_get(0,0,SYM(colour )); + _0_get(0,0,SYM(contrast )); + _0_get(0,0,SYM(whiteness )); + _0_get(0,0,SYM(frequency )); + } else if (attr==SYM(brightness)) { PICTURE_ATTR_GET(brightness); + } else if (attr==SYM(hue )) { PICTURE_ATTR_GET(hue ); + } else if (attr==SYM(colour )) { PICTURE_ATTR_GET(colour ); + } else if (attr==SYM(contrast )) { PICTURE_ATTR_GET(contrast ); + } else if (attr==SYM(whiteness )) { PICTURE_ATTR_GET(whiteness ); + } else if (attr==SYM(frequency )) { + int fd = GETFD; + int value; + WIOCTL(fd, VIDIOCGFREQ, &value); + {Ruby argv[3] ={INT2NUM(1), SYM(frequency), INT2NUM(value)}; send_out(COUNT(argv),argv);} + } else { RAISE("What you say?"); } +} + +\def void close () { + if (image) rb_funcall(rself,SI(dealloc_image),0); + rb_call_super(argc,argv); +} + +\def void _0_colorspace (Symbol c) { + if (c==SYM(RGB24)) palette=VIDEO_PALETTE_RGB24; + else if (c==SYM(YUV420P)) palette=VIDEO_PALETTE_YUV420P; + else RAISE("supported: RGB24, YUV420P"); + + int fd = GETFD; + VideoPicture *gp = new VideoPicture; + WIOCTL(fd, VIDIOCGPICT, gp); + gp->palette = palette; + WIOCTL(fd, VIDIOCSPICT, gp); + WIOCTL(fd, VIDIOCGPICT, gp); + //if (bit_packing) { delete bit_packing; bit_packing=0; } + switch(palette) { + case VIDEO_PALETTE_RGB24:{ + uint32 masks[3] = { 0xff0000,0x00ff00,0x0000ff }; + bit_packing = new BitPacking(is_le(),3,3,masks); + }break; + case VIDEO_PALETTE_YUV420P:{ + // woops, special case already, can't do that with bit_packing + } + default: + RAISE("can't handle palette %d", gp->palette); + } + delete gp; +} + +\def void initialize2 () { + int fd = GETFD; + VideoPicture *gp = new VideoPicture; +/* long flags; + fcntl(fd,F_GETFL,&flags); + flags |= O_NONBLOCK; + fcntl(fd,F_SETFL,&flags); */ + + WIOCTL(fd, VIDIOCGCAP, &vcaps); + gfpost(&vcaps); + rb_funcall(rself,SI(_0_size),2,INT2NUM(vcaps.maxheight),INT2NUM(vcaps.maxwidth)); + WIOCTL(fd, VIDIOCGPICT, gp); + gfpost(gp); + char buf[1024] = ""; + int n = 17 /*COUNT(video_palette_choice)*/; + for (int i=0; i<n; i++) { + gp->palette = i; + ioctl(fd, VIDIOCSPICT, gp); + ioctl(fd, VIDIOCGPICT, gp); + if (gp->palette == i) { + if (*buf) strcpy(buf+strlen(buf),", "); + //strcpy(buf+strlen(buf),video_palette_choice[i].name); + sprintf(buf+strlen(buf),"%d",i); + } + } + gfpost("This card supports palettes: %s", buf); + _0_colorspace(0,0,SYM(RGB24)); + rb_funcall(rself,SI(_0_channel),1,INT2NUM(0)); + delete gp; +} + +\def void initialize (Symbol mode, String filename, Symbol option=Qnil) { + rb_call_super(argc,argv); + image = Pt<uint8>(); + rb_ivar_set(rself,SI(@stream), + rb_funcall(rb_cFile,SI(open),2,filename,rb_str_new2("r+"))); + rb_funcall(rself,SI(initialize2),0); +} + +\classinfo { + IEVAL(rself,"install '#io:videodev',1,1;@flags=4;@comment='Video4linux 1.x'"); +} +\end class FormatVideoDev +void startup_videodev () { + \startall +} diff --git a/externals/gridflow/format/x11.c b/externals/gridflow/format/x11.c new file mode 100644 index 00000000..62041f06 --- /dev/null +++ b/externals/gridflow/format/x11.c @@ -0,0 +1,655 @@ +/* + $Id: x11.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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. +*/ + +#include "../base/grid.h.fcs" +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/time.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/StringDefs.h> + +/* X11 Error Handler type */ +typedef int (*XEH)(Display *, XErrorEvent *); + +#ifdef HAVE_X11_SHARED_MEMORY +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#endif + +\class FormatX11 < Format +struct FormatX11 : Format { + template <class T> void frame_by_type (T bogus); +/* at the Display/Screen level */ + Display *display; /* connection to xserver */ + Visual *visual; /* screen properties */ + Window root_window; + Colormap colormap; /* for 256-color mode */ + long black,white; /* color numbers in default colormap */ + short depth; + bool use_shm; /* should use shared memory? */ + bool use_stripes; /* use alternate conversion in 256-color mode */ + +/* at the Window level */ + int autodraw; /* how much to send to the display at once */ + Window window; /* X11 window number */ + Window parent; /* X11 window number of the parent */ + GC imagegc; /* X11 graphics context (like java.awt.Graphics) */ + XImage *ximage; /* X11 image descriptor */ + char *name; /* window name (for use by window manager) */ + Pt<uint8> image; /* the real data (that XImage binds to) */ + bool is_owner; + int pos_x, pos_y; + + P<BitPacking> bit_packing; + P<Dim> dim; + bool lock_size; + bool override_redirect; + +#ifdef HAVE_X11_SHARED_MEMORY + XShmSegmentInfo *shm_info; /* to share memory with X11/Unix */ +#endif + + Atom wmProtocolsAtom; + Atom wmDeleteAtom; + + FormatX11 () : use_stripes(false), + autodraw(1), window(0), ximage(0), name(0), image(Pt<uint8>()), is_owner(true), + dim(0), lock_size(false), override_redirect(false) +#ifdef HAVE_X11_SHARED_MEMORY + , shm_info(0) +#endif + {} + + void show_section(int x, int y, int sx, int sy); + void set_wm_hints (int sx, int sy); + void dealloc_image (); + bool alloc_image (int sx, int sy); + void resize_window (int sx, int sy); + void open_display(const char *disp_string); + void report_pointer(int y, int x, int state); + void prepare_colormap(); + Window FormatX11::search_window_tree (Window xid, Atom key, const char *value, int level=0); + + \decl void initialize (...); + \decl void frame (); + \decl void close (); + \decl void call (); + \decl void _0_out_size (int sy, int sx); + \decl void _0_draw (); + \decl void _0_autodraw (int autodraw); + \decl void _0_setcursor (int shape); + \decl void _0_hidecursor (); + \decl void _0_set_geometry (int y, int x, int sy, int sx); + \decl void _0_fall_thru (int flag); + \grin 0 int +}; + +/* ---------------------------------------------------------------- */ + +void FormatX11::show_section(int x, int y, int sx, int sy) { + if (y>dim->get(0)||x>dim->get(1)) return; + if (y+sy>dim->get(0)) sy=dim->get(0)-y; + if (x+sx>dim->get(1)) sx=dim->get(1)-x; +#ifdef HAVE_X11_SHARED_MEMORY + if (use_shm) { + XSync(display,False); + XShmPutImage(display,window,imagegc,ximage,x,y,x,y,sx,sy,False); + /* should completion events be waited for? looks like a bug */ + } else +#endif + XPutImage(display,window,imagegc,ximage,x,y,x,y,sx,sy); + XFlush(display); +} + +/* window manager hints, defines the window as non-resizable */ +void FormatX11::set_wm_hints (int sx, int sy) { + if (!is_owner) return; + XWMHints wmhints; + XTextProperty window_name, icon_name; + XSizeHints hints; + // enable recommended, maximum and minimum size + hints.flags=PSize|PMaxSize|PMinSize; + // set those + hints.min_width = hints.max_width = hints.width = sx; + hints.min_height = hints.max_height = hints.height = sy; + + wmhints.input = True; + wmhints.flags = InputHint; + XStringListToTextProperty((char **)&name, 1, &window_name); + XStringListToTextProperty((char **)&name, 1, &icon_name); + XSetWMProperties(display, window, + &window_name, &icon_name, NULL, 0, &hints, &wmhints, NULL); +} + +void FormatX11::report_pointer(int y, int x, int state) { + Ruby argv[5] = { + INT2NUM(0), SYM(position), + INT2NUM(y), INT2NUM(x), INT2NUM(state) }; + send_out(COUNT(argv),argv); +} + +\def void call() { + XEvent e; + for (;;) { + int xpending = XEventsQueued(display, QueuedAfterFlush); + if (!xpending) break; + XNextEvent(display,&e); + switch (e.type) { + case Expose:{ + XExposeEvent *ex = (XExposeEvent *)&e; + if (rb_ivar_get(rself,SI(@mode)) == SYM(out)) { + show_section(ex->x,ex->y,ex->width,ex->height); + } + }break; + case ButtonPress:{ + XButtonEvent *eb = (XButtonEvent *)&e; + eb->state |= 128<<eb->button; + report_pointer(eb->y,eb->x,eb->state); + }break; + case ButtonRelease:{ + XButtonEvent *eb = (XButtonEvent *)&e; + eb->state &= ~(128<<eb->button); + report_pointer(eb->y,eb->x,eb->state); + }break; + case KeyPress: + case KeyRelease:{ + XKeyEvent *ek = (XKeyEvent *)&e; + //XLookupString(ek, buf, 63, 0, 0); + char *kss = XKeysymToString(XLookupKeysym(ek, 0)); + char buf[64]; + if (!kss) return; /* unknown keys ignored */ + if (isdigit(*kss)) sprintf(buf,"D%s",kss); else strcpy(buf,kss); + Ruby argv[6] = { + INT2NUM(0), e.type==KeyPress ? SYM(keypress) : SYM(keyrelease), + INT2NUM(ek->y), INT2NUM(ek->x), INT2NUM(ek->state), + rb_funcall(rb_str_new2(buf),SI(intern),0) }; + send_out(COUNT(argv),argv); + //XFree(kss); + }break; + case MotionNotify:{ + XMotionEvent *em = (XMotionEvent *)&e; + report_pointer(em->y,em->x,em->state); + }break; + case DestroyNotify:{ + gfpost("This window is being closed, so this handler will close too!"); + rb_funcall(rself,SI(close),0); + return; + }break; + case ConfigureNotify:break; // as if we cared + case ClientMessage:{ + // tnx to vektor&walken + /* + if (e.xclient.message_type==wmProtocolsAtom + && e.xclient.format==32 + && (Atom)(e.xclient.data.l[0])==wmDeleteAtom) { + gfpost("This window is being closed, so this handler will close too!"); + rb_funcall(rself,SI(close),0); + return; + } + */ + }break; + } + } + IEVAL(rself,"@clock.delay 20"); +} + +\def void frame () { + XGetSubImage(display, window, + 0, 0, dim->get(1), dim->get(0), + (unsigned)-1, ZPixmap, ximage, 0, 0); + GridOutlet out(this,0,dim,NumberTypeE_find(rb_ivar_get(rself,SI(@cast)))); + int sy=dim->get(0), sx=dim->get(1); + int bs=dim->prod(1); + STACK_ARRAY(uint8,b2,bs); + for(int y=0; y<sy; y++) { + Pt<uint8> b1 = Pt<uint8>(image,ximage->bytes_per_line*dim->get(0)) + + ximage->bytes_per_line * y; + bit_packing->unpack(sx,b1,b2); + out.send(bs,b2); + } +} + +/* loathe Xlib's error handlers */ +static FormatX11 *current_x11; +static int FormatX11_error_handler (Display *d, XErrorEvent *xee) { + gfpost("X11 reports Error: display=0x%08x",(int)d); + gfpost("serial=0x%08x error=0x%08x request=0x%08lx minor=0x%08x", + xee->serial, xee->error_code, xee->request_code, xee->minor_code); + current_x11->use_shm = false; + return 42; /* it seems that the return value is ignored. */ +} + +void FormatX11::dealloc_image () { + if (!ximage) return; + if (use_shm) { + #ifdef HAVE_X11_SHARED_MEMORY + shmdt(ximage->data); + XShmDetach(display,shm_info); + if (shm_info) {delete shm_info; shm_info=0;} + //ximage->data = new char[1]; // bogus + //ximage->data = 0; + //XDestroyImage(ximage); + XFree(ximage); + ximage = 0; + image = Pt<uint8>(); + #endif + } else { + //XDestroyImage(ximage); + XFree(ximage); + ximage = 0; + image = Pt<uint8>(); + } +} + +bool FormatX11::alloc_image (int sx, int sy) { + dim = new Dim(sy, sx, 3); + dealloc_image(); + if (sx==0 || sy==0) return false; +#ifdef HAVE_X11_SHARED_MEMORY + if (use_shm) { + shm_info = new XShmSegmentInfo; + ximage = XShmCreateImage(display,visual,depth,ZPixmap,0,shm_info,sx,sy); + if (!ximage) { + gfpost("shm got disabled (1), retrying..."); + return alloc_image(sx,sy);} + shm_info->shmid = shmget(IPC_PRIVATE, + ximage->bytes_per_line*ximage->height, IPC_CREAT|0777); + if(shm_info->shmid < 0) + RAISE("ERROR: shmget failed: %s",strerror(errno)); + ximage->data = shm_info->shmaddr = + (char *)shmat(shm_info->shmid,0,0); + image = Pt<uint8>((uint8 *)ximage->data, + ximage->bytes_per_line*sy); + shm_info->readOnly = False; + current_x11 = this; + //XSetErrorHandler(FormatX11_error_handler); + if (!XShmAttach(display, shm_info)) RAISE("ERROR: XShmAttach: big problem"); + XSync(display,0); // make sure the server picks it up + //XSetErrorHandler(0); + /* yes, this can be done now. should cause auto-cleanup. */ + shmctl(shm_info->shmid,IPC_RMID,0); + if (!use_shm) { + gfpost("shm got disabled (2), retrying..."); + return alloc_image(sx,sy);} + } else +#endif + ximage = XCreateImage(display,visual,depth,ZPixmap,0,0,sx,sy,8,0); + if (!ximage) RAISE("can't create image"); + image = ARRAY_NEW(uint8,ximage->bytes_per_line*sy); + ximage->data = (int8 *)image; + int status = XInitImage(ximage); + if (status!=1) gfpost("XInitImage returned: %d", status); + return true; +} + +void FormatX11::resize_window (int sx, int sy) { + if (sy<16) sy=16; if (sy>4096) RAISE("height too big"); + if (sx<16) sx=16; if (sx>4096) RAISE("width too big"); + alloc_image(sx,sy); + if (name) delete name; + name = new char[64]; + sprintf(name,"GridFlow (%d,%d,3)",sy,sx); + if (window) { + if (is_owner && !lock_size) { + set_wm_hints(sx,sy); + XResizeWindow(display,window,sx,sy); + } + } else { + XSetWindowAttributes xswa; + xswa.do_not_propagate_mask = 0; //? + xswa.override_redirect = override_redirect; //#!@#$ + window = XCreateWindow(display, + parent, pos_x, pos_y, sx, sy, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWOverrideRedirect|CWDontPropagate, &xswa); + if(!window) RAISE("can't create window"); + set_wm_hints(sx,sy); + if (is_owner) { + // fall_thru 0 + XSelectInput(display, window, + ExposureMask | StructureNotifyMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | + KeyPressMask | KeyReleaseMask); + XMapRaised(display, window); + } else { + // fall_thru 1 + XSelectInput(display, window, + ExposureMask | StructureNotifyMask); + } + imagegc = XCreateGC(display, window, 0, NULL); + if (visual->c_class == PseudoColor) prepare_colormap(); + } + XSync(display,0); +} + +GRID_INLET(FormatX11,0) { + if (in->dim->n != 3) + RAISE("expecting 3 dimensions: rows,columns,channels"); + if (in->dim->get(2)!=3 && in->dim->get(2)!=4) + RAISE("expecting 3 or 4 channels: red,green,blue,ignored (got %d)",in->dim->get(2)); + int sxc = in->dim->prod(1); + int sx = in->dim->get(1), osx = dim->get(1); + int sy = in->dim->get(0), osy = dim->get(0); + in->set_factor(sxc); + if (sx!=osx || sy!=osy) resize_window(sx,sy); + if (in->dim->get(2)!=bit_packing->size) { + bit_packing->mask[3]=0; + bit_packing = new BitPacking(bit_packing->endian, + bit_packing->bytes, in->dim->get(2), bit_packing->mask); + } +} GRID_FLOW { + int bypl = ximage->bytes_per_line; + int sxc = in->dim->prod(1); + int sx = in->dim->get(1); + int y = in->dex/sxc; + int oy = y; + for (; n>0; y++, data+=sxc, n-=sxc) { + // convert line + if (use_stripes) { + int o=y*bypl; + for (int x=0, i=0, k=y%3; x<sx; x++, i+=3, k=(k+1)%3) { + image[o+x] = (k<<6) | data[i+k]>>2; + } + } else { + bit_packing->pack(sx, data, image+y*bypl); + } + } + if (autodraw==2) show_section(0,oy,sx,y-oy); +} GRID_FINISH { + if (autodraw==1) show_section(0,0,in->dim->get(1),in->dim->get(0)); +} GRID_END + +\def void close () { + if (!this) RAISE("stupid error: trying to close display NULL. =)"); + bit_packing=0; + IEVAL(rself,"@clock.unset"); + if (is_owner) XDestroyWindow(display,window); + XSync(display,0); + dealloc_image(); + XCloseDisplay(display); + display=0; + rb_call_super(argc,argv); +} + +\def void _0_out_size (int sy, int sx) { resize_window(sx,sy); } +\def void _0_draw () { show_section(0,0,dim->get(1),dim->get(0)); } + +\def void _0_autodraw (int autodraw) { + if (autodraw<0 || autodraw>2) + RAISE("autodraw=%d is out of range",autodraw); + this->autodraw = autodraw; +} + +\def void _0_setcursor (int shape) { + shape = 2*(shape&63); + Cursor c = XCreateFontCursor(display,shape); + XDefineCursor(display,window,c); + XFlush(display); +} + +\def void _0_hidecursor () { + Font font = XLoadFont(display,"fixed"); + XColor color; /* bogus */ + Cursor c = XCreateGlyphCursor(display,font,font,' ',' ',&color,&color); + XDefineCursor(display,window,c); + XFlush(display); +} + +void FormatX11::prepare_colormap() { + Colormap colormap = XCreateColormap(display,window,visual,AllocAll); + XColor colors[256]; + if (use_stripes) { + for (int i=0; i<192; i++) { + int k=(i&63)*0xffff/63; + colors[i].pixel = i; + colors[i].red = (i>>6)==0 ? k : 0; + colors[i].green = (i>>6)==1 ? k : 0; + colors[i].blue = (i>>6)==2 ? k : 0; + colors[i].flags = DoRed | DoGreen | DoBlue; + } + XStoreColors(display,colormap,colors,192); + } else { + for (int i=0; i<256; i++) { + colors[i].pixel = i; + colors[i].red = ((i>>0)&7)*0xffff/7; + colors[i].green = ((i>>3)&7)*0xffff/7; + colors[i].blue = ((i>>6)&3)*0xffff/3; + colors[i].flags = DoRed | DoGreen | DoBlue; + } + XStoreColors(display,colormap,colors,256); + } + XSetWindowColormap(display,window,colormap); +} + +void FormatX11::open_display(const char *disp_string) { + int screen_num; + Screen *screen; + + // Open an X11 connection + display = XOpenDisplay(disp_string); + if(!display) RAISE("ERROR: opening X11 display: %s",strerror(errno)); + + // btw don't expect too much from Xlib error handling. + // Xlib, you are so free of the ravages of intelligence... + XSetErrorHandler(FormatX11_error_handler); + + screen = DefaultScreenOfDisplay(display); + screen_num = DefaultScreen(display); + visual = DefaultVisual(display, screen_num); + white = XWhitePixel(display,screen_num); + black = XBlackPixel(display,screen_num); + root_window = DefaultRootWindow(display); + depth = DefaultDepthOfScreen(screen); + colormap = 0; + + switch(visual->c_class) { + case TrueColor: case DirectColor: /* without colormap */ + break; + case PseudoColor: /* with colormap */ + if (depth!=8) + RAISE("ERROR: with colormap, only supported depth is 8 (got %d)", + depth); + break; + default: RAISE("ERROR: visual type not supported (got %d)", + visual->c_class); + } + +#ifdef HAVE_X11_SHARED_MEMORY + use_shm = !! XShmQueryExtension(display); +#else + use_shm = false; +#endif +} + +Window FormatX11::search_window_tree (Window xid, Atom key, const char *value, int level) { + if (level>2) return 0xDeadBeef; + Window root_r, parent_r; + Window *children_r; + unsigned int nchildren_r; + XQueryTree(display,xid,&root_r,&parent_r,&children_r,&nchildren_r); + Window target = 0xDeadBeef; + for (int i=0; i<(int)nchildren_r; i++) { + Atom actual_type_r; + int actual_format_r; + unsigned long nitems_r, bytes_after_r; + unsigned char *prop_r; + XGetWindowProperty(display,children_r[i],key,0,666,0,AnyPropertyType, + &actual_type_r,&actual_format_r,&nitems_r,&bytes_after_r,&prop_r); + uint32 value_l = strlen(value); + bool match = prop_r && nitems_r>=value_l && + strncmp((char *)prop_r+nitems_r-value_l,value,value_l)==0; + XFree(prop_r); + if (match) {target=children_r[i]; break;} + target = search_window_tree(children_r[i],key,value,level+1); + if (target != 0xDeadBeef) break; + } + if (children_r) XFree(children_r); + return target; +} + +\def void _0_set_geometry (int y, int x, int sy, int sx) { + pos_x = x; + pos_y = y; + XMoveWindow(display,window,x,y); + resize_window(sx,sy); + XFlush(display); +} + +\def void _0_fall_thru (int flag) { + if (flag) { + gfpost("falling through!"); + XSelectInput(display, window, + ExposureMask | StructureNotifyMask); + } else { + gfpost("NOT falling through!"); + XSelectInput(display, window, + ExposureMask | StructureNotifyMask | + PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask); + } + XFlush(display); +} + +\def void initialize (...) { + int sy=240, sx=320; // defaults + rb_call_super(argc,argv); + argv++, argc--; + VALUE domain = argc<1 ? SYM(here) : argv[0]; + int i; + if (domain==SYM(here)) { + open_display(0); + i=1; + } else if (domain==SYM(local)) { + if (argc<2) RAISE("open x11 local: not enough args"); + char host[256]; + int dispnum = NUM2INT(argv[1]); + sprintf(host,":%d",dispnum); + open_display(host); + i=2; + } else if (domain==SYM(remote)) { + if (argc<3) RAISE("open x11 remote: not enough args"); + char host[256]; + strcpy(host,rb_sym_name(argv[1])); + int dispnum = NUM2INT(argv[2]); + sprintf(host+strlen(host),":%d",dispnum); + open_display(host); + i=3; + } else if (domain==SYM(display)) { + if (argc<2) RAISE("open x11 display: not enough args"); + char host[256]; + strcpy(host,rb_sym_name(argv[1])); + for (int k=0; host[k]; k++) if (host[k]=='%') host[k]==':'; + gfpost("mode `display', DISPLAY=`%s'",host); + open_display(host); + i=2; + } else { + RAISE("x11 destination syntax error"); + } + + for(;i<argc;i++) { + Ruby a=argv[i]; + if (a==SYM(override_redirect)) { + override_redirect = true; + } else if (a==SYM(use_stripes)){ + use_stripes = true; + } else { + break; + } + } + + pos_x=pos_y=0; + parent = root_window; + if (i>=argc) { + } else { + VALUE winspec = argv[i]; + if (winspec==SYM(root)) { + window = root_window; + is_owner = false; + } else if (winspec==SYM(embed)) { + Ruby title_s = rb_funcall(argv[i+1],SI(to_s),0); + char *title = strdup(rb_str_ptr(title_s)); + sy = sx = pos_y = pos_x = 0; + parent = search_window_tree(root_window,XInternAtom(display,"WM_NAME",0),title); + free(title); + if (parent == 0xDeadBeef) RAISE("Window not found."); + } else if (winspec==SYM(embed_by_id)) { + const char *winspec2 = rb_sym_name(argv[i+1]); + if (strncmp(winspec2,"0x",2)==0) { + parent = strtol(winspec2+2,0,16); + } else { + parent = atoi(winspec2); + } + } else { + if (TYPE(winspec)==T_SYMBOL) { + const char *winspec2 = rb_sym_name(winspec); + if (strncmp(winspec2,"0x",2)==0) { + window = strtol(winspec2+2,0,16); + } else { + window = atoi(winspec2); // huh? + } + } else { + window = INT(winspec); + } + is_owner = false; + sy = sx = pos_y = pos_x = 0; + } + } + + // "resize" also takes care of creation + resize_window(sx,sy); + + if (is_owner) { + wmProtocolsAtom = XInternAtom(display, "WM_PROTOCOLS", False); + wmDeleteAtom = XInternAtom(display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(display,window,&wmDeleteAtom,1); + } + + Visual *v = visual; + int disp_is_le = !ImageByteOrder(display); + int bpp = ximage->bits_per_pixel; + switch(visual->c_class) { + case TrueColor: case DirectColor: { + uint32 masks[3] = { v->red_mask, v->green_mask, v->blue_mask }; + bit_packing = new BitPacking(disp_is_le, bpp/8, 3, masks); + } break; + case PseudoColor: { + uint32 masks[3] = { 0x07, 0x38, 0xC0 }; + bit_packing = new BitPacking(disp_is_le, bpp/8, 3, masks); + } break; + } + IEVAL(rself,"@clock = Clock.new self; @clock.delay 0"); +} + +\classinfo { + IEVAL(rself,"install '#io:x11',1,1;@mode=6;@comment='X Window System Version 11.5'"); +} +\end class FormatX11 +void startup_x11 () { + \startall +} diff --git a/externals/gridflow/optional/usb.c b/externals/gridflow/optional/usb.c new file mode 100644 index 00000000..1721d4c4 --- /dev/null +++ b/externals/gridflow/optional/usb.c @@ -0,0 +1,358 @@ +/* + $Id: usb.c,v 1.1 2005-10-04 02:02:15 matju Exp $ + + GridFlow + Copyright (c) 2001,2002,2003 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. +*/ + +#include <usb.h> +#include "../base/grid.h.fcs" + +static Ruby cUSB; + +struct named_int {const char *name; int v;}; + +named_int usb_class_choice[] = { + {"USB_CLASS_PER_INTERFACE",0}, + {"USB_CLASS_AUDIO",1}, + {"USB_CLASS_COMM",2}, + {"USB_CLASS_HID",3}, + {"USB_CLASS_PRINTER",7}, + {"USB_CLASS_MASS_STORAGE",8}, + {"USB_CLASS_HUB",9}, + {"USB_CLASS_DATA",10}, + {"USB_CLASS_VENDOR_SPEC",0xff}, + {0,0}, +}; + +named_int usb_descriptor_types_choices[] = { + {"USB_DT_DEVICE",0x01}, + {"USB_DT_CONFIG",0x02}, + {"USB_DT_STRING",0x03}, + {"USB_DT_INTERFACE",0x04}, + {"USB_DT_ENDPOINT",0x05}, + {"USB_DT_HID",0x21}, + {"USB_DT_REPORT",0x22}, + {"USB_DT_PHYSICAL",0x23}, + {"USB_DT_HUB",0x29}, + {0,0}, +}; + +named_int usb_descriptor_types_sizes[] = { + {"USB_DT_DEVICE_SIZE",18}, + {"USB_DT_CONFIG_SIZE",9}, + {"USB_DT_INTERFACE_SIZE",9}, + {"USB_DT_ENDPOINT_SIZE",7}, + {"USB_DT_ENDPOINT_AUDIO_SIZE",9},/* Audio extension */ + {"USB_DT_HUB_NONVAR_SIZE",7}, + {0,0}, +}; + +named_int usb_endpoints_choices[] = { + {"USB_ENDPOINT_ADDRESS_MASK",0x0f},/* in bEndpointAddress */ + {"USB_ENDPOINT_DIR_MASK",0x80}, + {"USB_ENDPOINT_TYPE_MASK",0x03},/* in bmAttributes */ + {"USB_ENDPOINT_TYPE_CONTROL",0}, + {"USB_ENDPOINT_TYPE_ISOCHRONOUS",1}, + {"USB_ENDPOINT_TYPE_BULK",2}, + {"USB_ENDPOINT_TYPE_INTERRUPT",3}, + {0,0}, +}; + +named_int usb_requests_choice[] = { + {"USB_REQ_GET_STATUS",0x00}, + {"USB_REQ_CLEAR_FEATURE",0x01}, + {"USB_REQ_SET_FEATURE",0x03}, + {"USB_REQ_SET_ADDRESS",0x05}, + {"USB_REQ_GET_DESCRIPTOR",0x06}, + {"USB_REQ_SET_DESCRIPTOR",0x07}, + {"USB_REQ_GET_CONFIGURATION",0x08}, + {"USB_REQ_SET_CONFIGURATION",0x09}, + {"USB_REQ_GET_INTERFACE",0x0A}, + {"USB_REQ_SET_INTERFACE",0x0B}, + {"USB_REQ_SYNCH_FRAME",0x0C}, + {0,0}, +}; + +named_int usb_type_choice[] = { + {"USB_TYPE_STANDARD",(0x00 << 5)}, + {"USB_TYPE_CLASS",(0x01 << 5)}, + {"USB_TYPE_VENDOR",(0x02 << 5)}, + {"USB_TYPE_RESERVED",(0x03 << 5)}, + {0,0}, +}; + +named_int usb_recipient_choice[] = { + {"USB_RECIP_DEVICE",0x00}, + {"USB_RECIP_INTERFACE",0x01}, + {"USB_RECIP_ENDPOINT",0x02}, + {"USB_RECIP_OTHER",0x03}, + {0,0}, +}; + +named_int usb_misc[] = { + {"USB_MAXENDPOINTS",32}, + {"USB_MAXINTERFACES",32}, + {"USB_MAXALTSETTING",128}, + {"USB_MAXCONFIG",8}, + {"USB_ENDPOINT_IN",0x80}, + {"USB_ENDPOINT_OUT",0x00}, + {"USB_ERROR_BEGIN",500000}, + {0,0}, +}; + +named_int* usb_all_defines[] = { + usb_class_choice, + usb_descriptor_types_choices, + usb_descriptor_types_sizes, + usb_endpoints_choices, + usb_requests_choice, + usb_type_choice, + usb_recipient_choice, + usb_misc, +}; + +#define COMMA , + +//14 +#define USB_DEVICE_DESCRIPTOR(MANGLE,SEP) \ + MANGLE(bLength)SEP\ + MANGLE(bDescriptorType)SEP\ + MANGLE(bcdUSB)SEP\ + MANGLE(bDeviceClass)SEP\ + MANGLE(bDeviceSubClass)SEP\ + MANGLE(bDeviceProtocol)SEP\ + MANGLE(bMaxPacketSize0)SEP\ + MANGLE(idVendor)SEP\ + MANGLE(idProduct)SEP\ + MANGLE(bcdDevice)SEP\ + MANGLE(iManufacturer)SEP\ + MANGLE(iProduct)SEP\ + MANGLE(iSerialNumber)SEP\ + MANGLE(bNumConfigurations) + +//8 +#define USB_ENDPOINT_DESCRIPTOR(MANGLE,SEP) \ + MANGLE(bLength)SEP\ + MANGLE(bDescriptorType)SEP\ + MANGLE(bEndpointAddress)SEP\ + MANGLE(bmAttributes)SEP\ + MANGLE(wMaxPacketSize)SEP\ + MANGLE(bInterval)SEP\ + MANGLE(bRefresh)SEP\ + MANGLE(bSynchAddress) +// MANGLE(extras) + +//9 +#define USB_INTERFACE_DESCRIPTOR(MANGLE,SEP) \ + MANGLE(bLength)SEP\ + MANGLE(bDescriptorType)SEP\ + MANGLE(bInterfaceNumber)SEP\ + MANGLE(bAlternateSetting)SEP\ + MANGLE(bNumEndpoints)SEP\ + MANGLE(bInterfaceClass)SEP\ + MANGLE(bInterfaceSubClass)SEP\ + MANGLE(bInterfaceProtocol)SEP\ + MANGLE(iInterface) +// MANGLE(endpoint) +// MANGLE(extras) + +//8 +#define USB_CONFIG_DESCRIPTOR(MANGLE,SEP) \ + MANGLE(bLength)SEP\ + MANGLE(bDescriptorType)SEP\ + MANGLE(wTotalLength)SEP\ + MANGLE(bNumInterfaces)SEP\ + MANGLE(bConfigurationValue)SEP\ + MANGLE(iConfiguration)SEP\ + MANGLE(bmAttributes)SEP\ + MANGLE(MaxPower) +// MANGLE(interface) +// MANGLE(extras) + +static Ruby usb_get_endpoint (struct usb_endpoint_descriptor *self) { +#define MANGLE(X) INT2NUM(self->X) + return rb_funcall(rb_const_get(cUSB,SI(Endpoint)),SI(new),8, + USB_ENDPOINT_DESCRIPTOR(MANGLE,COMMA)); +#undef MANGLE +} + +static Ruby usb_get_interface (struct usb_interface_descriptor *self) { +#define MANGLE(X) INT2NUM(self->X) + return rb_funcall(rb_const_get(cUSB,SI(Interface)),SI(new),9+1, + USB_INTERFACE_DESCRIPTOR(MANGLE,COMMA), + usb_get_endpoint(self->endpoint)); +#undef MANGLE +} + +static Ruby usb_get_config (struct usb_config_descriptor *self) { + if (!self) { + fprintf(stderr,"warning: usb_get_config: null pointer\n"); + return Qnil; + } +#define MANGLE(X) INT2NUM(self->X) + Ruby interface = rb_ary_new(); + for (int i=0; i<self->interface->num_altsetting; i++) { + rb_ary_push(interface, usb_get_interface(&self->interface->altsetting[i])); + } + return rb_funcall(rb_const_get(cUSB,SI(Config)),SI(new),8+1, + USB_CONFIG_DESCRIPTOR(MANGLE,COMMA), interface); +#undef MANGLE +} + +static Ruby usb_scan_bus (usb_bus *bus) { + Ruby rbus = rb_ary_new(); + for (struct usb_device *dev=bus->devices; dev; dev=dev->next) { + struct usb_device_descriptor *devd = &dev->descriptor; + Ruby config = rb_ary_new(); + for (int i=0; i<devd->bNumConfigurations; i++) { + rb_ary_push(config,usb_get_config(&dev->config[i])); + } +#define MANGLE(X) INT2NUM(devd->X) + rb_ary_push(rbus, rb_funcall(rb_const_get(cUSB,SI(Device)),SI(new),14+3, + USB_DEVICE_DESCRIPTOR(MANGLE,COMMA), + rb_str_new2(dev->filename), + PTR2FIX(dev), + config)); +#undef MANGLE + } + return rbus; +} + +\class USB < CObject +class USB : public CObject { + usb_dev_handle *h; +public: + \decl void initialize (Ruby dev); + \decl int close (); + \decl int bulk_write (int ep, String s, int timeout); + \decl int bulk_read (int ep, String s, int timeout); + \decl int claim_interface(int interface); + \decl int release_interface(int interface); + \decl int set_configuration(int configuration); + \decl int set_altinterface(int alternate); + \decl int control_msg(int requesttype, int request, int value, int index, String s, int timeout); + \decl int resetep(int ep); + \decl int clear_halt(int ep); + \decl int reset(); + //\decl int get_string(int index, int langid, String s); + //\decl int get_string_simple(int index, String s); +}; + +\def void initialize (Ruby dev) { + Ruby ptr = rb_funcall(dev,SI(ptr),0); + rb_ivar_set(rself, SI(@dev), ptr); + h = usb_open(FIX2PTR(struct usb_device,ptr)); + if (!h) RAISE("usb_open returned null handle"); +} + +\def int close () { + if (!h) RAISE("USB closed"); + int r = usb_close(h); + h=0; + return r; +} + +#define TRAP(stuff) int r=(stuff); if (r<0) RAISE("%s", usb_strerror()); else return r; + +\def int bulk_write (int ep, String s, int timeout) { + if (!h) RAISE("USB closed"); + TRAP(usb_bulk_write(h, ep, rb_str_ptr(s), rb_str_len(s), timeout));} +\def int bulk_read (int ep, String s, int timeout) { + if (!h) RAISE("USB closed"); + gfpost("%d, '%s', %d",ep,rb_str_ptr(s),timeout); + TRAP(usb_bulk_read(h, ep, rb_str_ptr(s), rb_str_len(s), timeout));} +\def int claim_interface(int interface) { + TRAP(usb_claim_interface(h, interface));} +\def int release_interface(int interface) { + TRAP(usb_release_interface(h, interface));} +\def int set_configuration(int configuration) { + TRAP(usb_set_configuration(h, configuration));} +\def int set_altinterface(int alternate) { + TRAP(usb_set_altinterface(h, alternate));} +\def int control_msg(int requesttype, int request, int value, int index, String s, int timeout) { + TRAP(usb_control_msg(h, requesttype, request, value, index, rb_str_ptr(s), rb_str_len(s), timeout));} +\def int resetep(int ep) { + TRAP(usb_resetep(h, ep));} +\def int clear_halt(int ep) { + TRAP(usb_clear_halt(h, ep));} +\def int reset() { + TRAP(usb_reset(h));} +/*\def int get_string(int index, int langid, String s) { + TRAP(usb_get_string(h, index, langid, rb_str_ptr(s), rb_str_len(s)));}*/ +/*\def int get_string_simple(int index, String s) { + TRAP(usb_get_string_simple(h, index, rb_str_ptr(s), rb_str_len(s)));}*/ + +// not handled yet: +// struct usb_string_descriptor +// struct usb_hid_descriptor +// void usb_set_debug(int level); +// Device usb_device(USB dev); +// get_string and get_string_simple (not present in libusb 0.1.4) + +\classinfo +\end class USB + +static Ruby USB_s_new(Ruby argc, Ruby *argv, Ruby qlass) { + USB *self = new USB(); + Ruby rself = Data_Wrap_Struct(qlass, 0, CObject_free, self); + self->rself = rself; + Ruby keep = rb_ivar_get(mGridFlow, rb_intern("@fobjects")); + rb_hash_aset(keep,rself,Qtrue); /* prevent sweeping (leak) (!@#$ WHAT??) */ + rb_funcall2(rself,SI(initialize),argc,argv); + return rself; +} + +#define SDEF(_class_,_name_,_argc_) \ + rb_define_singleton_method(c##_class_,#_name_,(RMethod)_class_##_s_##_name_,_argc_) + +void startup_usb () { + cUSB = rb_define_class_under(mGridFlow, "USB", rb_cObject); + //rb_define_singleton_method(cUSB, "new", USB_new, 1); + EVAL("class Symbol; def decap; x=to_s; x[0..0]=x[0..0].downcase; x.intern end end"); + SDEF(USB,new,-1); + define_many_methods(cUSB, ciUSB.methodsn, ciUSB.methods); +#define MANGLE(X) rb_funcall(SYM(X),SI(decap),0) + rb_const_set(cUSB, SI(Device), rb_funcall(EVAL("Struct"),SI(new),14+3, + USB_DEVICE_DESCRIPTOR(MANGLE,COMMA), SYM(filename), SYM(ptr), SYM(config))); + rb_const_set(cUSB, SI(Endpoint), rb_funcall(EVAL("Struct"),SI(new),8, + USB_ENDPOINT_DESCRIPTOR(MANGLE,COMMA))); + rb_const_set(cUSB, SI(Interface), rb_funcall(EVAL("Struct"),SI(new),9+1, + USB_INTERFACE_DESCRIPTOR(MANGLE,COMMA), SYM(endpoint))); + rb_const_set(cUSB, SI(Config), rb_funcall(EVAL("Struct"),SI(new),8+1, + USB_CONFIG_DESCRIPTOR(MANGLE,COMMA), SYM(interface))); +#undef MANGLE + for(int i=0; i<COUNT(usb_all_defines); i++) { + named_int *ud = usb_all_defines[i]; + for(; ud->name; ud++) { + rb_const_set(cUSB, rb_intern(ud->name), INT2NUM(ud->v)); + } + } + //usb_set_debug(42); + usb_init(); + usb_find_busses(); + usb_find_devices(); + Ruby busses = rb_hash_new(); + rb_ivar_set(cUSB, SI(@busses), busses); + for (usb_bus *bus=usb_get_busses(); bus; bus=bus->next) { + rb_hash_aset(busses,rb_str_new2(bus->dirname),usb_scan_bus(bus)); + } + //IEVAL(cUSB,"STDERR.print '@busses = '; STDERR.puts @busses.inspect"); +} + |