Initial revision
diff --git a/Demo/sgi/video/README b/Demo/sgi/video/README
new file mode 100644
index 0000000..8b87582
--- /dev/null
+++ b/Demo/sgi/video/README
@@ -0,0 +1,56 @@
+This directory contains Python and C code we wrote while we had a
+framegrabber board on loan from SGI.
+
+	--Guido and Jack
+
+cam.py		network real-time tv broadcast; see tv.py
+		usage: cam [packfactor [host]]
+		specifying 'all' for host broadcasts
+
+camcorder.py	record video movies or make snapshots (in movie format)
+		usage: camcorder [-c] [-a audiohost [-s]] [moviefile]
+		-c		color (default b/w, packfactor = 2)
+		-a audiohost	syncaudio is running on audiohost
+		-s		start syncaudio (on audiohost)
+		moviefile	(default film.video)
+		keyboard commands:
+		s	stop grabbing (single step if already stopped)
+		c	continuous grabbing
+		r	start recording
+		p	pause recording (record single frame if paused)
+		ESC	quit
+
+statit.py	various statistics operations on movie files
+
+syncaudio.py	record audio synchronized with camcorder -a
+		usage: syncaudio videohost soundfile
+		soundfile format: 16 bits, 16khz, mono
+
+tv.py		receiver for transmissions from cam.py
+
+video.py	player for movies recorded by camcorder.py
+		usage: video [moviefile [soundfile]]
+		default moviefile is film.video
+		default sound is no sound
+
+vinfo.py	print a summary of a movie file
+
+vtime.py	virtual time module imported by syncaudio.py and camcorder.py
+
+
+These are C programs, either for efficiency or because they need to
+link with a C library.
+
+squash.c	make a movie smaller by averaging pixels
+		usage: squash factor [bits] <moviefile >newmoviefile
+		factor		x and y compression factor
+		bits		#bits left per sample in result (default 8)
+
+squash2.c	make a movie smaller by dropping pixels
+		usage: squash2 factor <moviefile >newmoviefile
+		factor		x and y compression factor
+
+tomono.c	like squash2 but outputs a monochrome movie
+
+v2i.c		convert the first image of a movie file to SGI .rgb format
+		link with -limage
diff --git a/Demo/sgi/video/cam.py b/Demo/sgi/video/cam.py
new file mode 100755
index 0000000..f30e1e0
--- /dev/null
+++ b/Demo/sgi/video/cam.py
@@ -0,0 +1,129 @@
+import sys
+from socket import *
+from gl import *
+from GL import *
+from DEVICE import *
+from time import millitimer
+
+HS = 40 # Header size (must be same as in tv.py)
+
+# Rely on UDP packet (de)fragmentation for smoother images
+# (Changed for broadcast)
+MAX = 16000
+
+PF = 2 # Default packfactor
+
+# Default receiver station is voorn.
+# Kwik has no yellow pages, so...
+HOST = '192.16.201.121'
+PORT = 5555
+
+if sys.argv[1:]:
+	PF = eval(sys.argv[1])
+
+if sys.argv[2:]:
+	HOST = sys.argv[2]
+	if HOST = 'all':
+		HOST = '<broadcast>'
+		MAX = 1400
+
+PF2 = PF*PF
+
+def main():
+	centerx, centery = 400, 300
+
+	foreground()
+	wid = winopen('cam')
+	RGBmode()
+	doublebuffer()
+	gconfig()
+	qdevice(ESCKEY)
+
+	w, h = getsize()
+	ortho2(0, w, 0, h)
+	w = w/PF*PF
+	h = h/PF*PF
+
+	readsource(SRC_FRAMEGRABBER)
+
+	s = socket(AF_INET, SOCK_DGRAM)
+	if HOST = '<broadcast>':
+		s.allowbroadcast(1)
+	addr = HOST, PORT
+
+	bytesperline = w/PF2
+	linesperchunk = MAX/bytesperline
+	linesperchunk = linesperchunk/PF*PF
+	nchunks = (h+linesperchunk-1)/linesperchunk
+
+	print 'MAX=', MAX,
+	print 'linesperchunk=', linesperchunk,
+	print 'nchunks=', nchunks,
+	print 'w=', w, 'h=', h
+
+	x1, x2 = 0, w-1
+
+	t1 = millitimer()
+	nframes = 0
+	fps = 0
+
+	msg = ''
+
+	while 1:
+		while qtest():
+			dev, val = qread()
+			if dev = REDRAW:
+				reshapeviewport()
+				w, h = getsize()
+				ortho2(0, w, 0, h)
+				w = w/PF*PF
+				h = h/PF*PF
+
+				bytesperline = w/PF2
+				linesperchunk = MAX/bytesperline
+				linesperchunk = linesperchunk/PF*PF
+				nchunks = (h+linesperchunk-1)/linesperchunk
+
+				print 'MAX=', MAX,
+				print 'linesperchunk=', linesperchunk,
+				print 'nchunks=', nchunks,
+				print 'w=', w, 'h=', h
+
+				x1, x2 = 0, w-1
+
+				fps = 0
+
+			elif dev = ESCKEY:
+				winclose(wid)
+				return
+
+		readsource(SRC_FRAMEGRABBER)
+
+		nframes = nframes+1
+		if nframes >= fps:
+			t2 = millitimer()
+			if t2 <> t1:
+				fps = int(10000.0*nframes/(t2-t1)) * 0.1
+				msg = `fps` +  ' frames/sec'
+				t1 = t2
+				nframes = 0
+
+		RGBcolor(255,255,255)
+		cmov2i(9,9)
+		charstr(msg)
+
+		swapbuffers()
+		rectcopy(centerx-w/2, centery-w/2, centerx+w/2, centery+w/2, 0, 0)
+
+		for i in range(nchunks):
+			y1 = i*linesperchunk
+			y2 = y1 + linesperchunk-1
+			if y2 >= h: y2 = h-1
+			data = lrectread(x1, y1, x2, y2)
+			data2 = packrect(x2-x1+1, y2-y1+1, PF, data)
+			prefix = `w, h, PF, x1, y1, x2, y2`
+			prefix = prefix + ' ' * (HS-len(prefix))
+			data3 = prefix + data2
+			s.sendto(data3, addr)
+
+main()
diff --git a/Demo/sgi/video/camcorder.py b/Demo/sgi/video/camcorder.py
new file mode 100755
index 0000000..a6b7f4a
--- /dev/null
+++ b/Demo/sgi/video/camcorder.py
@@ -0,0 +1,197 @@
+#!/ufs/guido/bin/sgi/python3.3
+from gl import *
+from GL import *
+from DEVICE import *
+import time
+import sys
+import getopt
+import socket
+import posix
+import vtime
+
+SYNCPORT = 10000
+CTLPORT = 10001
+
+VP_GBXORG = 0x1000001
+VP_GBYORG = 0x1000002
+VP_FBXORG = 0x1000003
+VP_FBYORG = 0x1000004
+VP_WIDTH =  0x1000005
+VP_HEIGHT = 0x1000006
+
+class Struct(): pass
+epoch = Struct()
+
+def getvideosize():
+    w = getvideo(VP_WIDTH)
+    h = getvideo(VP_HEIGHT)
+    print getvideo(VP_GBXORG), getvideo(VP_GBYORG)
+    print getvideo(VP_FBXORG), getvideo(VP_FBYORG)
+    x = 0
+    y = 0
+    return x,y,w,h
+def saveframe(f,x,y,w,h,pf, notime):
+    readsource(SRC_FRONT)
+    if pf:
+    	w = w/pf*pf
+    	h = h/pf*pf
+    data = None
+    data = lrectread(x,y,x+w-1,y+h-1)
+    if pf: data = packrect(w,h,pf,data)
+    if notime: t = 0
+    else: t = time.millitimer()-epoch.epoch
+    f.write(`t` + ',' + `len(data)` + '\n')
+    f.write(data)
+    readsource(SRC_FRAMEGRABBER)
+def drawframe(x,y,w,h,col):
+    drawmode(OVERDRAW)
+    color(col)
+    bgnline()
+    v2i(x-1,y-1) ; v2i(x+w,y-1); v2i(x+w,y+h); v2i(x-1,y+h); v2i(x-1,y-1)
+    endline()
+    drawmode(NORMALDRAW)
+def main():
+    foreground()
+    pf = 2
+    ausync = 0
+    austart = 0
+    optlist, args = getopt.getopt(sys.argv[1:],'ca:s')
+    for opt, arg in optlist:
+	if opt = '-c':
+	    pf = 0
+	elif opt = '-a':
+	    ausync = 1
+	    aumachine = arg
+	elif opt = '-s':
+	    austart = 1
+	else:
+	    print 'Usage: camcorder [-c] [-a audiomachine [-s]]'
+	    sys.exit(1)
+    if austart:
+	if not ausync:
+	    print 'Cannot use -s without -a'
+	    sys.exit(1)
+	print 'Starting audio recorder...'
+	posix.system('rsh '+aumachine+' syncrecord '+socket.gethostname()+' &')
+    if ausync:
+	print 'Syncing to audio recorder...'
+	globtime = vtime.VTime().init(1,aumachine,SYNCPORT)
+	ctl = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
+	ctl.bind((socket.gethostname(),CTLPORT))
+	aua = (socket.gethostbyname(aumachine), CTLPORT)
+	print 'Done.'
+    vidx, vidy, w, h = getvideosize()
+    prefsize(w,h)
+    win = winopen('Camcorder')
+    if len(args) > 1:
+        f = open(args, 'w')
+    else:
+        f = open('film.video', 'w')
+    w, h = getsize()
+    realw, realh = w, h
+    doublebuffer()
+    RGBmode()
+    gconfig()
+    qdevice(LEFTMOUSE)
+    qdevice(RKEY)
+    qdevice(SKEY)
+    qdevice(CKEY)
+    qdevice(PKEY)
+    qdevice(ESCKEY)
+    inrunning = 1
+    outrunning = 0
+    stop = 'stop'
+    readsource(SRC_FRAMEGRABBER)
+    mousing = 0
+    epoch.epoch = time.millitimer()
+    stoptime = epoch.epoch
+    sizewritten = 0
+    x, y = realw/4, realh/4
+    w, h = w/2, h/2
+    drawframe(x,y,w,h,1)
+    nframe = 0
+    try:
+	num = 0
+	while 1:
+	    insingle = 0
+	    outsingle = 0
+	    if mousing:
+		drawframe(x,y,w,h,0)
+		ox, oy = getorigin()
+		if sizewritten:
+		    x = getvaluator(MOUSEX)-ox
+		    y = getvaluator(MOUSEY)-oy
+		else:
+		    w = getvaluator(MOUSEX)-x-ox
+		    h = getvaluator(MOUSEY)-y-oy
+		drawframe(x,y,w,h,1)
+	    if qtest():
+		ev, val = qread()
+		if ev = LEFTMOUSE and val = 1:
+		    drawframe(x,y,w,h,0)
+		    mousing = 1
+		    ox, oy = getorigin()
+		    x = getvaluator(MOUSEX)-ox
+		    y = getvaluator(MOUSEY)-oy
+		elif ev = LEFTMOUSE and val = 0:
+		    mousing = 0
+		    if not sizewritten:
+		    	f.write('CMIF video 1.0\n')
+			f.write(`w,h,pf` + '\n')
+			sizewritten = 1
+		if ev = RKEY and val = 1:
+		    if not inrunning:
+			ringbell()
+		    else:
+			outrunning = 1
+			wasstopped = time.millitimer() - stoptime
+			epoch.epoch = epoch.epoch + wasstopped
+			nframe = 0
+			starttime = time.millitimer()
+			if ausync:
+			    ctl.sendto(`(1,starttime)`, aua)
+		elif ev = PKEY and val = 1 and outrunning:
+		    outrunning = 0
+		    stoptime = time.millitimer()
+		    if ausync:
+			ctl.sendto(`(0,stoptime)`, aua)
+		    nf =  nframe * 1000.0 / (time.millitimer()-starttime)
+		    drawmode(OVERDRAW)
+		    color(0)
+		    clear()
+		    color(1)
+		    cmov2i(5,5)
+		    charstr('Recorded ' + `nf` + ' frames/sec')
+		    drawmode(NORMALDRAW)
+		elif ev = PKEY and val = 1 and not outrunning:
+			outsingle = 1
+		elif ev = CKEY and val = 1:
+			inrunning = 1
+		elif ev = SKEY and val = 1:
+			if outrunning:
+			    ringbell()
+			elif inrunning:
+			    inrunning = 0
+			else:
+			    insingle = 1
+		elif ev = ESCKEY:
+		    if ausync:
+			ctl.sendto(`(2,time.millitimer())`, aua)
+		    raise stop
+	    if inrunning or insingle:
+		rectcopy(vidx,vidy,realw,realh,0,0)
+	        swapbuffers()
+	    if outrunning or outsingle:
+		nframe = nframe + 1
+		if not sizewritten:
+		    f.write('CMIF video 1.0\n')
+		    f.write(`w,h,pf` + '\n')
+		    sizewritten = 1
+		saveframe(f, x, y, w, h, pf, outsingle)
+    except stop:
+	pass
+    drawmode(OVERDRAW)
+    color(0)
+    clear()
+#
+main()
diff --git a/Demo/sgi/video/squash.c b/Demo/sgi/video/squash.c
new file mode 100755
index 0000000..c045084
--- /dev/null
+++ b/Demo/sgi/video/squash.c
@@ -0,0 +1,120 @@
+#include <stdio.h>
+
+long *bm;
+long h, w;
+long factor;
+
+#define OC(x,xi) ((x)*factor+(xi))
+#define BM(x,xi,y,yi) bm[OC(y,yi)*w+OC(x,xi)]
+
+#define COMP(r,g,b) ((r) | ((g)<<8) | ((b) << 16))
+
+#define R(comp) ((comp) & 0xff)
+#define G(comp) (((comp)>>8) & 0xff)
+#define B(comp) (((comp)>>16) & 0xff)
+
+main(argc, argv)
+    char **argv;
+{
+    char lbuf[100];
+    int nh, nw;
+    int x, y, xi, yi;
+    int num;
+    int r, g, b;
+    long data;
+    long *nbm, *nbmp;
+    int i;
+    int bits, mask, roundbit, addbit;
+    int pf;
+    int newfmt = 0;
+
+    if( argc != 2 && argc != 3) {
+       fprintf(stderr, "Usage: squash factor [bits]\n");
+       exit(1);
+    }
+    factor = atoi(argv[1]);
+    if ( argc > 2 ) {
+	bits = atoi(argv[2]);
+	mask = (1 << bits) - 1;
+	mask <<= (8-bits);
+	roundbit = 1 << (7-bits);
+	addbit = 1 << (8-bits);
+	fprintf(stderr, "%x %x %x\n", mask, roundbit, addbit);
+    } else {
+	mask = 0xff;
+	roundbit = 0;
+	addbit = 0;
+    }
+    gets(lbuf);
+    if ( strncmp( lbuf, "CMIF", 4) == 0 ) {
+	newfmt = 1;
+	gets(lbuf);
+	if( sscanf(lbuf, "(%d,%d,%d)", &w, &h, &pf) != 3) {
+	    fprintf(stderr, "%s: bad size spec: %s\n", argv[0], lbuf);
+	    exit(1);
+	}
+	if ( pf != 0 ) {
+	    fprintf(stderr, "%s: packed file\n", argv[0]);
+	    exit(1);
+	}
+    } else {
+	if ( sscanf(lbuf, "(%d,%d)", &w, &h) != 2) {
+	    fprintf(stderr, "%s: bad size spec: %s\n", argv[0], lbuf);
+	    exit(1);
+	}
+    }
+    nh = h / factor;
+    nw = w / factor;
+    if ( newfmt )
+	printf("CMIF video 1.0\n(%d,%d,%d)\n", nw, nh, 0);
+    else
+	printf("(%d,%d)\n", nw, nh);
+    if ( (bm = (long *)malloc(h*w*sizeof(long))) == 0) {
+	fprintf(stderr, "%s: No memory\n", argv[0]);
+	exit(1);
+    }
+    if ( (nbm = (long *)malloc(nh*nw*sizeof(long))) == 0) {
+	fprintf(stderr, "%s: No memory\n", argv[0]);
+	exit(1);
+    }
+    while( !feof(stdin) ) {
+	gets(lbuf);
+	if ( feof(stdin) ) break;
+	puts(lbuf);
+	fprintf(stderr, "Reading %d\n", h*w*sizeof(long));
+	if ( (i=fread(bm, 1, h*w*sizeof(long), stdin)) != h*w*sizeof(long)) {
+	    fprintf(stderr, "%s: short read, %d wanted %d\n", argv[0],
+		i, h*w*sizeof(long));
+	    exit(1);
+	}
+	nbmp = nbm;
+	for( y=0; y<nh; y++) {
+	    for ( x=0; x<nw; x++) {
+		r = g = b = 0;
+		num = 0;
+		for( xi=0; xi<factor; xi++ ) {
+		    for(yi=0; yi<factor; yi++) {
+			if ( y*factor+yi < h && x*factor+xi < w ) {
+			    num++;
+			    data = BM(x,xi,y,yi);
+			    r += R(data);
+			    g += G(data);
+			    b += B(data);
+			}
+			else fprintf(stderr, "skip %d %d %d %d\n", x, xi, y, yi);
+		    }
+		}
+		r = r/num; g = g/num; b = b/num;
+		if ( (r & mask) != mask && ( r & roundbit) ) r += addbit;
+		if ( (g & mask) != mask && ( g & roundbit) ) g += addbit;
+		if ( (b & mask) != mask && ( b & roundbit) ) b += addbit;
+		data = COMP(r, g, b);
+		*nbmp++ = data;
+	    }
+	}
+	if (nbmp - nbm != nh * nw ) fprintf(stderr, "%d %d\n", nbmp-nbm, nh*nw);
+	fprintf(stderr, "Writing %d\n", (nbmp-nbm)*sizeof(long));
+	fwrite(nbm, 1, (nbmp-nbm)*sizeof(long), stdout);
+    }
+    exit(0);
+}
diff --git a/Demo/sgi/video/squash2.c b/Demo/sgi/video/squash2.c
new file mode 100755
index 0000000..c4d03e9
--- /dev/null
+++ b/Demo/sgi/video/squash2.c
@@ -0,0 +1,72 @@
+#include <stdio.h>
+
+long *bm;
+long h, w;
+long factor;
+
+#define OC(x,xi) ((x)*factor+(xi))
+#define BM(x,xi,y,yi) bm[OC(y,yi)*w+OC(x,xi)]
+
+#define COMP(r,g,b) ((r) | ((g)<<8) | ((b) << 16))
+
+#define R(comp) ((comp) & 0xff)
+#define G(comp) (((comp)>>8) & 0xff)
+#define B(comp) (((comp)>>16) & 0xff)
+
+main(argc, argv)
+    char **argv;
+{
+    char lbuf[100];
+    int nh, nw;
+    int x, y, xi, yi;
+    int num;
+    int r, g, b;
+    long data;
+    long *nbm, *nbmp;
+    int i;
+
+    if( argc != 2) {
+       fprintf(stderr, "Usage: squash factor\n");
+       exit(1);
+    }
+    factor = atoi(argv[1]);
+    gets(lbuf);
+    if ( sscanf(lbuf, "(%d,%d)", &w, &h) != 2) {
+	fprintf(stderr, "%s: bad size spec: %s\n", argv[0], lbuf);
+	exit(1);
+    }
+    nh = h / factor;
+    nw = w / factor;
+    printf("(%d,%d)\n", nw, nh);
+    if ( (bm = (long *)malloc(h*w*sizeof(long))) == 0) {
+	fprintf(stderr, "%s: No memory\n", argv[0]);
+	exit(1);
+    }
+    if ( (nbm = (long *)malloc(nh*nw*sizeof(long))) == 0) {
+	fprintf(stderr, "%s: No memory\n", argv[0]);
+	exit(1);
+    }
+    while( !feof(stdin) ) {
+	gets(lbuf);
+	if ( feof(stdin) ) break;
+	puts(lbuf);
+	fprintf(stderr, "Reading %d\n", h*w*sizeof(long));
+	if ( (i=fread(bm, 1, h*w*sizeof(long), stdin)) != h*w*sizeof(long)) {
+	    fprintf(stderr, "%s: short read, %d wanted %d\n", argv[0],
+		i, h*w*sizeof(long));
+	    exit(1);
+	}
+	nbmp = nbm;
+	for( y=0; y<nh; y++) {
+	    for ( x=0; x<nw; x++) {
+		r = g = b = 0;
+		num = 0;
+		*nbmp++ = BM(x,0,y,0);
+	    }
+	}
+	if (nbmp - nbm != nh * nw ) fprintf(stderr, "%d %d\n", nbmp-nbm, nh*nw);
+	fprintf(stderr, "Writing %d\n", (nbmp-nbm)*sizeof(long));
+	fwrite(nbm, 1, (nbmp-nbm)*sizeof(long), stdout);
+    }
+    exit(0);
+}
diff --git a/Demo/sgi/video/statit.py b/Demo/sgi/video/statit.py
new file mode 100755
index 0000000..360e49e
--- /dev/null
+++ b/Demo/sgi/video/statit.py
@@ -0,0 +1,115 @@
+import sys
+from time import millitimer
+
+def main():
+	filename = 'film2.video'
+	if sys.argv[1:]: filename = sys.argv[1]
+	f = open(filename, 'r')
+
+	line = f.readline()
+	w, h = eval(line[:-1])
+	w2, h2 = w/2, h/2
+	size = w2 * h2
+
+	data = data2 = t = t0 = t1 = None
+	nframes = 0
+	t0 = millitimer()
+	while 1:
+		line = f.readline()
+		if not line: break
+		t = eval(line[:-1])
+		data = None
+		data = f.read(size)
+		if len(data) <> size:
+			raise EOFError
+		dostat(w2, h2, data)
+		nframes = nframes+1
+	t1 = millitimer()
+	
+	t = 0.001 * (t1-t0)
+	fps = 0.1 * int(10*nframes/t)
+	print nframes, 'frames in', t, 'sec. =', fps, 'frames/sec.'
+
+def dostat(w, h, data):
+	print
+	stat3(w, h, data)
+
+# Statistic op 1: frequencies of byte values
+def stat1(w, h, data):
+	bins = [0]*256
+	for c in data:
+		i = ord(c)
+		bins[i] = bins[i]+1
+	prbins(bins)
+
+def prbins(bins):
+	import string
+	s = ''
+	tot = 0
+	for i in range(256):
+		tot = tot + bins[i]
+		s = s + string.rjust(`bins[i]`, 4)
+		if len(s) >= 4*16:
+			print s, string.rjust(`tot`, 7)
+			s = ''
+			tot = 0
+
+# Statistic op 2: run lengths
+def stat2(w, h, data):
+	runs = []
+	for y in range(h):
+		count, value = 0, ord(data[y*w])
+		for c in data[y*w : y*w+w]:
+			i = ord(c)
+			if i <> value:
+				runs.append(count, value)
+				count, value = 0, i
+			count = count+1
+		runs.append(count, value)
+	print len(runs), 'runs =', 0.1 * (10*w*h/len(runs)), 'bytes/run'
+
+# Statistic op 3: frequencies of byte differences
+def stat3(w, h, data):
+	bins = [0]*256
+	prev = 0
+	for c in data:
+		i = ord(c)
+		delta = divmod(i-prev, 256)[1]
+		prev = i
+		bins[delta] = bins[delta]+1
+	prbins(bins)
+
+# Try packing
+def packblock(w, h, data):
+	res = ''
+	for y in range(h):
+		res = res + packline(data[y*w : y*w+w])
+	return res
+
+def packline(line):
+	bytes = []
+	for c in line:
+		bytes.append(ord(c))
+	prev = bytes[0]
+	i, n = 1, len(bytes)
+	while i < n:
+		for pack in (0, 2, 4, 8):
+			if pack = 0:
+				lo, hi = 0, 0
+			else:
+				hi = pow(2, pack-1)-1
+				lo = -hi-1
+			p = prev
+			j = i
+			count = 0
+			while j < n:
+				x = bytes[j]
+				delta = byte(x-p)
+				if not lo <= delta <= hi:
+					break
+				p = x
+				j = j+1
+
+def byte(x): return divmod(x, 256)[1]
+
+main()
diff --git a/Demo/sgi/video/syncaudio.py b/Demo/sgi/video/syncaudio.py
new file mode 100755
index 0000000..742a433
--- /dev/null
+++ b/Demo/sgi/video/syncaudio.py
@@ -0,0 +1,94 @@
+import AL
+import al
+import sys
+import vtime
+import socket
+import time
+
+
+SLEEPTIME = 500		# 500 ms sleeps
+SAMPLEFREQ = 16000	# 16Khz samples
+SAMPLERATE = AL.RATE_16000
+NEEDBUFFERED = SAMPLEFREQ	# Buffer 1 second of sound
+BUFFERSIZE = NEEDBUFFERED*4	# setqueuesize() par for 2 second sound
+
+AVSYNCPORT = 10000	# Port for time syncing
+AVCTLPORT = 10001	# Port for record start/stop
+
+def main():
+    if len(sys.argv) <> 3:
+	print 'Usage: ', sys.argv[0], 'videohostname soundfile'
+	sys.exit(1)
+    #
+    ofile = open(sys.argv[2], 'w')
+    #
+    globaltime = vtime.VTime().init(0,sys.argv[1],AVSYNCPORT)
+    #
+    ctl = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
+    ctl.bind((socket.gethostname(),AVCTLPORT))
+    #
+    inp = openmic()
+    #
+    out = 0		# Open aiff file
+    #
+    while 1:
+	if mainloop(None, ctl, inp, out, globaltime):
+	    break
+	if mainloop(ofile, ctl, inp, out, globaltime):
+	    break
+    pass	# Close aiff file
+    sys.exit(0)
+#
+def openmic():
+    conf = al.newconfig()
+    conf.setqueuesize(BUFFERSIZE)
+    conf.setwidth(AL.SAMPLE_16)
+    conf.setchannels(AL.MONO)
+    return al.openport('micr','r',conf)
+#
+def mainloop(ofile, ctl, inp, out, globaltime):
+    #
+    # Wait for sync packet, keeping 1-2 seconds of sound in the
+    # buffer
+    #
+    totsamps = 0
+    totbytes = 0
+    starttime = time.millitimer()
+    while 1:
+	time.millisleep(SLEEPTIME)
+	if ctl.avail():
+	    break
+	nsamples = inp.getfilled()-NEEDBUFFERED
+	if nsamples>0:
+	    data = inp.readsamps(nsamples)
+	    totsamps = totsamps + nsamples
+	    totbytes = totbytes + len(data)
+	    if ofile <> None:
+		ofile.write(data)
+    #
+    # Compute his starttime and the timestamp of the first byte in the
+    # buffer. Discard all buffered data upto his starttime
+    #
+    startstop,histime = eval(ctl.recv(100))
+    if (ofile = None and startstop = 0) or \
+			   (ofile <> None and startstop = 1):
+	print 'Sync error: saving=',save,' request=',startstop
+	sys.exit(1)
+    filllevel = inp.getfilled()
+    filltime = time.millitimer()
+    filltime = filltime - filllevel / (SAMPLEFREQ/1000)
+    starttime = globaltime.his2mine(histime)
+    nsamples = starttime - filltime
+    if nsamples < 0:
+	print 'Start/stop signal came too late'
+	sys.exit(1)
+    nsamples = nsamples * (SAMPLEFREQ / 1000)
+    data = inp.readsamps(nsamples)
+    totsamps = totsamps + nsamples
+    totbytes = totbytes + len(data)
+    print 'Time: ', time.millitimer()-starttime, ', Bytes: ', totbytes, ', Samples: ', totsamps
+    if ofile <> None:
+	ofile.write(data)
+    return (startstop = 2)
+
+main()
diff --git a/Demo/sgi/video/tomono.c b/Demo/sgi/video/tomono.c
new file mode 100755
index 0000000..546af68
--- /dev/null
+++ b/Demo/sgi/video/tomono.c
@@ -0,0 +1,165 @@
+#include <stdio.h>
+
+long *bm;
+long *nbm;
+long h, w;
+int nh, nw;
+long factor;
+
+#define OC(x,xi) ((x)*factor+(xi))
+#define BM(x,xi,y,yi) bm[OC(y,yi)*w+OC(x,xi)]
+
+#define COMP(r,g,b) ((r) | ((g)<<8) | ((b) << 16))
+
+#define R(comp) ((comp) & 0xff)
+#define G(comp) (((comp)>>8) & 0xff)
+#define B(comp) (((comp)>>16) & 0xff)
+
+#define CHOICEFUNC(np1, np2) ( random() & 1 )
+
+int inlevels = 3*255;
+int outlevels = 1;
+
+main(argc, argv)
+    char **argv;
+{
+    char lbuf[100];
+    int x, y, xi, yi;
+    int num;
+    int r, g, b;
+    long data;
+    int i;
+    double greyness;
+    int inpixels, outpixels;
+    int resid;
+
+    setvbuf(stdout, 0, _IOFBF, 1024*128);
+    if( argc != 2) {
+       fprintf(stderr, "Usage: tomono factor\n");
+       exit(1);
+    }
+    factor = atoi(argv[1]);
+    gets(lbuf);
+    if ( sscanf(lbuf, "(%d,%d)", &w, &h) != 2) {
+	fprintf(stderr, "%s: bad size spec: %s\n", argv[0], lbuf);
+	exit(1);
+    }
+    nh = h / factor;
+    nw = w / factor;
+    printf("(%d,%d)\n", nw, nh);
+    if ( (bm = (long *)malloc(h*w*sizeof(long))) == 0) {
+	fprintf(stderr, "%s: No memory\n", argv[0]);
+	exit(1);
+    }
+    if ( (nbm = (long *)malloc(nh*nw*sizeof(long))) == 0) {
+	fprintf(stderr, "%s: No memory\n", argv[0]);
+	exit(1);
+    }
+    while( !feof(stdin) ) {
+	gets(lbuf);
+	if ( feof(stdin) ) break;
+	puts(lbuf);
+	fprintf(stderr, "Reading %d\n", h*w*sizeof(long));
+	if ( (i=fread(bm, 1, h*w*sizeof(long), stdin)) != h*w*sizeof(long)) {
+	    fprintf(stderr, "%s: short read, %d wanted %d\n", argv[0],
+		i, h*w*sizeof(long));
+	    exit(1);
+	}
+	/*
+	** Compute picture blackness.
+	*/
+	inpixels = 0;
+	inpixels = countpixels(0,0,w,h);
+	greyness = (double)inpixels/(h*w*inlevels);
+	fprintf(stderr, "%3.1f%% grey\n", 100.0*greyness);
+	outpixels = (int)(greyness*outlevels*nh*nw);
+	fprintf(stderr, "Inpixels: %d (%d) Outpixels %d\n", inpixels, inpixels/inlevels, outpixels);
+	resid = fillpixels(0,0,nw,nh,0,0,w,h,outpixels);
+	if ( resid > 1 ) fprintf(stderr, "Residue: %d pixels\n", resid);
+	fprintf(stderr, "Writing %d\n", (nh*nw)*sizeof(long));
+	fwrite(nbm, 1, (nh*nw)*sizeof(long), stdout);
+    }
+    exit(0);
+}
+
+countpixels(x0,y0,x1,y1)
+{
+    int x, y, tot, data;
+
+    tot = 0;
+    for( y=y0; y<y1; y++)
+	for(x=x0; x<x1; x++) {
+	    data = bm[y*w+x];
+	    tot += R(data);
+	    tot += G(data);
+	    tot += B(data);
+    }
+    return tot;
+}
+
+fillpixels(x0,y0,x1,y1,ox0,oy0,ox1,oy1,npixels)
+{
+    int m, om, p1, p2, np1, np2, rp, resid;
+
+    if ( npixels == 0 ) return 0;
+    if ( x0+1 >= x1 && y0+1 >= y1 ) {
+	if ( npixels ) {
+	    nbm[y0*nw+x0] = 0xffffff;
+/* 	    fprintf(stderr, "->%d,%d\n", x0,y0); */
+	    return npixels - 1;
+	}
+	return 0;
+    }
+    if ( x1-x0 < y1-y0 ) {
+	if ( y1 - y0 <= 2 )
+	    m = y0 + 1;
+	else
+	    m = y0+1+(random()%(y1-y0-1));
+/* 	fprintf(stderr,"%d,%d %d,%d Y %d\n", x0, x1, y0, y1, m); */
+	/* om = (oy0+oy1)/2; */ om = m;
+	p1 = countpixels(ox0,oy0,ox1,om);
+	p2 = countpixels(ox0,om,ox1,oy1);
+	np1 = (int)(((float)p1/(p1+p2))*npixels);
+	np2 = (int)(((float)p2/(p1+p2))*npixels);
+	rp = npixels - np1 - np2;
+	if ( rp ) {
+	    np1 += rp/2;
+	    rp = rp - rp/2;
+	    np2 += rp;
+	}
+	resid = 0;
+	if ( CHOICEFUNC(np1, np2) ) {
+	    resid = fillpixels(x0,y0,x1,m,ox0,oy0,ox1,om,np1+resid);
+	    resid = fillpixels(x0,m,x1,y1,ox0,om,ox1,oy1,np2+resid);
+	} else {
+	    resid = fillpixels(x0,m,x1,y1,ox0,om,ox1,oy1,np2+resid);
+	    resid = fillpixels(x0,y0,x1,m,ox0,oy0,ox1,om,np1+resid);
+	}
+    } else {
+	if ( x1 - x0 <= 2 )
+	    m = x0 + 1;
+	else
+	    m = x0+1+(random()%(x1-x0-1));
+/* 	fprintf(stderr,"%d,%d %d,%d X %d\n", x0, x1, y0, y1, m); */
+	/* om = (ox0+ox1)/2; */ om = m;
+	p1 = countpixels(ox0,oy0,om,oy1);
+	p2 = countpixels(om,oy0,ox1,oy1);
+	np1 = (int)(((float)p1/(p1+p2))*npixels);
+	np2 = (int)(((float)p2/(p1+p2))*npixels);
+	rp = npixels - np1 - np2;
+	if ( rp ) {
+	    np1 += rp/2;
+	    rp = rp - rp/2;
+	    np2 += rp;
+	}
+	resid = 0;
+	if ( CHOICEFUNC(np1, np2) ) {
+	    resid = fillpixels(x0,y0,m,y1,ox0,oy0,om,oy1,np1+resid);
+	    resid = fillpixels(m,y0,x1,y1,om,oy0,ox1,oy1,np2+resid);
+	} else {
+	    resid = fillpixels(m,y0,x1,y1,om,oy0,ox1,oy1,np2+resid);
+	    resid = fillpixels(x0,y0,m,y1,ox0,oy0,om,oy1,np1+resid);
+	}
+    }
+    return resid;
+}
diff --git a/Demo/sgi/video/tv.py b/Demo/sgi/video/tv.py
new file mode 100755
index 0000000..621740c
--- /dev/null
+++ b/Demo/sgi/video/tv.py
@@ -0,0 +1,79 @@
+import string
+
+from socket import *
+from gl import *
+from GL import *
+from DEVICE import *
+from time import millisleep, millitimer
+
+PORT = 5555
+
+PF = 2 # packfactor
+HS = 40 # Header size
+
+def testimage():
+	RGBcolor(0, 0, 0)
+	clear()
+	RGBcolor(0, 255, 0)
+	cmov2i(10, 10)
+	charstr('Waiting...')
+
+def reshape():
+	reshapeviewport()
+	w, h = getsize()
+	ortho2(0, w, 0, h)
+	testimage()
+	return w, h
+
+def main():
+	s = socket(AF_INET, SOCK_DGRAM)
+	s.bind('', PORT)
+
+	foreground()
+	wid = winopen('tv')
+	RGBmode()
+	gconfig()
+	qdevice(ESCKEY)
+
+	oldw, oldh = getsize()
+	ortho2(0, oldw, 0, oldh)
+	testimage()
+
+	t1 = millitimer()
+
+	while 1:
+		if qtest():
+			dev, val = qread()
+			if dev = ESCKEY:
+				winclose(wid)
+				return
+			elif dev = REDRAW:
+				oldw, oldh = reshape()
+		elif s.avail():
+			data = s.recv(17000)
+			header = string.strip(data[:HS])
+			w, h, pf, x1, y1, x2, y2 = eval(header)
+			if (w, h) <> (oldw, oldh):
+				x, y = getorigin()
+				x, y = x-1, y+21 # TWM correction
+				winposition(x, x+w-1, y+oldh-h, y+oldh-1)
+				oldw, oldh = reshape()
+			data2 = data[HS:]
+			dx = (x2-x1+1)/pf
+			dy = (y2-y1+1)/pf
+			data3 = unpackrect(dx, dy, 1, data2)
+			rectzoom(pf, pf)
+			lrectwrite(x1, y1, x1+dx-1, y1+dy-1, data3)
+			t1 = millitimer()
+		else:
+			t2 = millitimer()
+			if t2-t1 >= 5000:
+				testimage()
+				t1 = t2
+			else:
+				millisleep(10)
+
+	winclose(wid)
+	return data
+
+main()
diff --git a/Demo/sgi/video/v2i.c b/Demo/sgi/video/v2i.c
new file mode 100755
index 0000000..41589d4
--- /dev/null
+++ b/Demo/sgi/video/v2i.c
@@ -0,0 +1,63 @@
+#include <stdio.h>
+#include <gl/image.h>
+
+long bm[1280];
+short rb[1280], gb[1280], bb[1280];
+long h, w;
+
+#define R(comp) ((comp) & 0xff)
+#define G(comp) (((comp)>>8) & 0xff)
+#define B(comp) (((comp)>>16) & 0xff)
+
+main(argc, argv)
+    char **argv;
+{
+    char lbuf[100];
+    int x, y;
+    int i;
+    IMAGE * of;
+    int pmask;
+
+    if( argc != 3 && argc != 4) {
+       fprintf(stderr, "Usage: v2i videofile imgfile [planemask]\n");
+       exit(1);
+    }
+    if ( argc == 4)
+	pmask = atoi(argv[3]);
+    else
+	pmask = 7;
+    if ( freopen(argv[1], "r", stdin) == NULL ) {
+	perror(argv[1]);
+	exit(1);
+    }
+    gets(lbuf);
+    if ( sscanf(lbuf, "(%d,%d)", &w, &h) != 2) {
+	fprintf(stderr, "%s: bad size spec: %s\n", argv[0], lbuf);
+	exit(1);
+    }
+    gets(lbuf);		/* Skip time info */
+    if ( w > 1280 ) {
+	fprintf(stderr, "%s: Sorry, too wide\n", argv[0]);
+	exit(1);
+    }
+    if ( (of=iopen(argv[2], "w", RLE(1), 3, w, h, 3)) == 0) {
+	perror(argv[2]);
+	exit(1);
+    }
+    for( y=0; y<h; y++) {
+	if( fread(bm, sizeof(long), w, stdin) != w) {
+	    fprintf(stderr, "%s: short read\n", argv[0]);
+	    exit(1);
+	}
+	for( x=0; x<w; x++) {
+	    if ( pmask & 1) rb[x] = R(bm[x]);
+	    if ( pmask & 2) gb[x] = G(bm[x]);
+	    if ( pmask & 4) bb[x] = B(bm[x]);
+	}
+	putrow(of, rb, y, 0);
+	putrow(of, gb, y, 1);
+	putrow(of, bb, y, 2);
+    }
+    iclose(of);
+    exit(0);
+}
diff --git a/Demo/sgi/video/video.py b/Demo/sgi/video/video.py
new file mode 100755
index 0000000..05c6794
--- /dev/null
+++ b/Demo/sgi/video/video.py
@@ -0,0 +1,112 @@
+from gl import *
+from GL import *
+from DEVICE import *
+import time
+import sys
+import al
+import AL
+
+BUFFERSIZE = 32000
+
+class Struct(): pass
+epoch = Struct()
+EndOfFile = 'End of file'
+bye = 'bye'
+
+def openspkr():
+    conf = al.newconfig()
+    conf.setqueuesize(BUFFERSIZE)
+    conf.setwidth(AL.SAMPLE_16)
+    conf.setchannels(AL.MONO)
+    return al.openport('spkr','w',conf)
+def openvideo(name):
+    f = open(name, 'r')
+    line = f.readline()
+    if not line: raise EndOfFile
+    if line[:4] = 'CMIF': line = f.readline()
+    x = eval(line[:-1])
+    if len(x) = 3: w, h, pf = x
+    else: w, h = x; pf = 2
+    return f, w, h, pf
+def loadframe(f,w,h,pf,af,spkr):
+    line = f.readline()
+    if line = '':
+	raise EndOfFile
+    x = eval(line[:-1])
+    if type(x) = type(0) or type(x) = type(0.0):
+    	tijd = x
+    	if pf = 0:
+    		size = w*h*4
+    	else:
+    		size = (w/pf) * (h/pf)
+    else:
+    	tijd, size = x
+    data = f.read(size)
+    if len(data) <> size:
+	raise EndOfFile
+    if pf:
+    	rectzoom(pf, pf)
+    	w = w/pf
+    	h = h/pf
+    	data = unpackrect(w, h, 1, data)
+    lrectwrite(0,0,w-1,h-1,data)
+    # This is ugly here, but the only way to get the two
+    # channels started in sync
+    #if af <> None:
+    #	playsound(af,spkr)
+    ct = time.millitimer() - epoch.epoch
+    if tijd > 0 and ct < tijd:
+    	time.millisleep(tijd-ct)
+    swapbuffers()
+    return tijd
+def playsound(af, spkr):
+    nsamp = spkr.getfillable()
+    data = af.read(nsamp*2)
+    spkr.writesamps(data)
+def main():
+    if len(sys.argv) > 1:
+	f, w, h, pf = openvideo(sys.argv[1])
+    else:
+	f, w, h, pf = openvideo('film.video')
+    af = None
+    spkr = None
+    if len(sys.argv) > 2:
+	af = open(sys.argv[2], 'r')
+	spkr = openspkr()
+    if len(sys.argv) > 3:
+	data = af.read(eval(sys.argv[3]))
+	del data
+    foreground()
+    prefsize(w,h)
+    win = winopen('Video player')
+    RGBmode()
+    doublebuffer()
+    gconfig()
+    qdevice(ESCKEY)
+    running = 1
+    epoch.epoch = time.millitimer()
+    nframe = 0
+    tijd = 1
+    try:
+	while 1:
+	    if running:
+		try:
+		    tijd = loadframe(f, w, h, pf, af, spkr)
+		    nframe = nframe + 1
+		except EndOfFile:
+		    running = 0
+		    t = time.millitimer()
+		    if tijd > 0:
+			    print 'Recorded at ', nframe * 1000.0 / tijd,
+			    print 'frames/second (', tijd, 'ms total)'
+		    print 'Played at', nframe * 1000.0 / (t-epoch.epoch),
+		    print 'frames/second'
+	    if af <> None:
+		playsound(af,spkr)
+	    if qtest():
+		if qread() = (ESCKEY,1):
+		    raise bye
+    except bye:
+	pass
+
+main()
diff --git a/Demo/sgi/video/vinfo.py b/Demo/sgi/video/vinfo.py
new file mode 100755
index 0000000..98b29fa
--- /dev/null
+++ b/Demo/sgi/video/vinfo.py
@@ -0,0 +1,55 @@
+#!/ufs/guido/bin/sgi/python3.3
+from gl import *
+from GL import *
+from DEVICE import *
+import time
+import sys
+
+class Struct(): pass
+epoch = Struct()
+EndOfFile = 'End of file'
+bye = 'bye'
+
+def openvideo(name):
+    f = open(name, 'r')
+    w, h = eval(f.readline()[:-1])
+    return f, w, h
+def loadframe(f, w, h):
+    tijd = f.readline()
+    if tijd = '':
+	raise EndOfFile
+    tijd = eval(tijd[:-1])
+    f.seek(w*h*4,1)
+    return tijd
+def saveframe(name, w, h, tijd, data):
+    f = open(name, 'w')
+    f.write(`w,h` + '\n')
+    f.write(`tijd` + '\n')
+    f.write(data)
+    f.close()
+def main():
+	if len(sys.argv) > 1:
+		names = sys.argv[1:]
+	else:
+		names = ['film.video']
+	for name in names:
+	    f, w, h = openvideo(name)
+	    print name+': '+`w`+'x'+`h`
+	    num = 0
+	    try:
+		while 1:
+		    try:
+			tijd = loadframe(f, w, h)
+			print '\t', tijd,
+			num = num + 1
+			if num % 8 = 0:
+				print
+		    except EndOfFile:
+			raise bye
+	    except bye:
+		pass
+	    if num % 8 <> 0:
+		print
+	    f.close()
+
+main()
diff --git a/Demo/sgi/video/vtime.py b/Demo/sgi/video/vtime.py
new file mode 100755
index 0000000..00d821e
--- /dev/null
+++ b/Demo/sgi/video/vtime.py
@@ -0,0 +1,106 @@
+#
+# Module vtime - Keep virtual time between two nodes.
+#
+# We try for synchronised clocks by sending a packet of the for
+# (1,mytime,0) to the other side, and waiting (at most) a second for
+# a reply. This reply has the form (2,mytime,histime), and we can
+# estimate the time difference by defining histime to be exactly half-way
+# between the time we sent our message and got our reply. We send a
+# final (3,mynewtime,histime) message to allow the other side to do the
+# same computations.
+#
+# Note that the protocol suffers heavily from the 2-army problem.
+# It'll have to do until I can read up on time-sync protocols, though.
+#
+from socket import *
+import time
+
+MSGSIZE = 100
+MSGTIMEOUT = 1000
+
+recv_timeout = 'receive timeout'
+bad_connect = 'Bad connection'
+
+def timeavg(a,b):
+    return int((long(a)+b)/2L)
+def tryrecv(s):
+    cnt = 0
+    while 1:
+	if s.avail():
+	    return s.recvfrom(MSGSIZE)
+	time.millisleep(100)
+	cnt = cnt + 100
+	if cnt > MSGTIMEOUT:
+	    raise recv_timeout
+
+class VTime():
+    def init(self,(client,host,port)):
+	s = socket(AF_INET, SOCK_DGRAM)
+	host = gethostbyname(host)
+	localhost = gethostbyname(gethostname())
+	raddr = (host,port)
+	s.bind((localhost,port))
+	if client:
+	    #
+	    # We loop here because we want the *second* measurement
+	    # for accuracy
+	    for loopct in (0,2):
+		curtijd = time.millitimer()
+		check = `(loopct,curtijd,0)`
+		s.sendto(check,raddr)
+		while 1:
+		    try:
+			if loopct:
+			    data, other = s.recvfrom(MSGSIZE)
+			else:
+			    data, other = tryrecv(s)
+			newtijd = time.millitimer()
+			if other <> raddr:
+			    print 'Someone else syncing to us: ', other
+			    raise bad_connect
+			data = eval(data)
+			if data[:2] = (loopct+1,curtijd):
+			    break
+			if data[0] <> 2:
+			    print 'Illegal sync reply: ', data
+			    raise bad_connect
+		    except recv_timeout:
+			curtijd = time.millitimer()
+			check = `(loopct,curtijd,0)`
+			s.sendto(check,raddr)
+	    histime = data[2]
+	    s.sendto(`(4,newtijd,histime)`,raddr)
+	    mytime = timeavg(curtijd,newtijd)
+	    #mytime = curtijd
+	    self.timediff = histime - mytime
+	else:
+	    while 1:
+		data,other = s.recvfrom(MSGSIZE)
+		if other <> raddr:
+		    print 'Someone else syncing to us: ', other, ' Wanted ', raddr
+		    raise bad_connect
+		data = eval(data)
+		if data[0] in (0,2):
+		    curtijd = time.millitimer()
+		    s.sendto(`(data[0]+1,data[1],curtijd)`,raddr)
+		elif data[0] = 4:
+		    newtijd = time.millitimer()
+		    histime = data[1]
+		    mytime = timeavg(curtijd,newtijd)
+		    #mytime = curtijd
+		    self.timediff = histime-mytime
+		    break
+		else:
+		    print 'Funny data: ', data
+		    raise bad_connect
+	return self
+	#
+    def his2mine(self,tijd):
+	return tijd - self.timediff
+    #
+    def mine2his(self, tijd):
+	return tijd + self.timediff
+
+def test(clt, host, port):
+    xx = VTime().init(clt,host,port)
+    print 'Time diff: ', xx.his2mine(0)