implement Esd; include "sys.m"; include "draw.m"; include "math.m"; include "arg.m"; include "esd.m"; sys : Sys; arg : Arg; math : Math; Connection : import Sys; Esd : module { init : fn(nil : ref Draw->Context, nil : list of string); }; SAME, DIFFERENT : con 1 << iota; port := ESD_PORT; debug := 1; pgrp : int; client_endian := SAME; init(nil : ref Draw->Context, args : list of string) { sys = load Sys Sys->PATH; arg = load Arg Arg->PATH; math = load Math Math->PATH; if (arg == nil || math == nil) { sys->raise(sys->sprint("Could not load %s : %r", Arg->PATH+"and "+Math->PATH)); } arg->init(args); pgrp = sys->pctl(sys->NEWPGRP, nil); while((c := arg->opt()) != 0) { case c { 'p' => port = int arg->arg(); * => usage(); exit; } } (n, conn) := sys->announce("tcp!*!"+string port); if (n < 0) { sys->print("Esd : announce failed : %r\n"); exit; } while (1) { listen(conn); } } listen(conn : Connection) { buf := array [sys->ATOMICIO] of byte; (ok, c) := sys->listen(conn); if (ok < 0) { error(sys->sprint("Listen failed : %r\n")); } rfd := sys->open(conn.dir + "/remote", Sys->OREAD); n := sys->read(rfd, buf, len buf); spawn hdlrthread(c); return; } hdlrthread(conn : Connection) { buf := array [sys->ATOMICIO] of byte; # The connections data file is not opened by default, # must explicitly do so to accept the new connection rdfd := sys->open(conn.dir + "/data", Sys->OREAD); wdfd := sys->open(conn.dir + "/data", Sys->OWRITE); rfd := sys->open(conn.dir + "/remote", Sys->OREAD); # The client IP is now available, once we have accepted connection. # The following will print the actual client IP address: iplen := sys->read(rfd, buf, len buf); debugprint(sys->sprint("Got new connection from %s", string buf[:iplen])); # Read Key. BUG: Currently ignore the key sent keylen := sys->read(rdfd, buf, ESD_KEY_LEN); if (keylen != ESD_KEY_LEN) { error(sys->sprint("Runt key from client, length %d", keylen)); } debugprint("client sent key :"+string buf[:keylen]); # Read Endianness. endianlen := sys->read(rdfd, buf, ESD_ENDIAN_LEN); if (endianlen != ESD_ENDIAN_LEN) { error(sys->sprint("Runt endianess from client, length %d", endianlen)); } debugprint("client sent endian: "+string buf[:endianlen]); if (int buf[0] == 'N') { client_endian = DIFFERENT; } # Send response debugprint("About to write response to client..."); if (sys->write(wdfd, scrub(OPCODE_RESPONSE), ESD_OPCODE_LEN) != ESD_OPCODE_LEN) { error(sys->sprint("Could not write response to client: %r")); } debugprint("Wrote response to client..."); # Currently only accept "stream" writes. Read the opcode type from client oplen := sys->read(rdfd, buf, ESD_OPCODE_LEN); if (oplen != ESD_OPCODE_LEN) { error(sys->sprint("Runt opcode from client, length %d", oplen)); } scrub(buf[:ESD_OPCODE_LEN]); if (buf[0] == OPCODE_STREAM[0] && buf[1] == OPCODE_STREAM[1] && buf[2] == OPCODE_STREAM[2] && buf[3] == OPCODE_STREAM[3]) { debugprint("got a stream opcode..."); # Read and configure format and rate if (sys->read(rdfd, buf, ESD_FMT_LEN+ESD_RATE_LEN) != ESD_FMT_LEN+ESD_RATE_LEN) { error("Runt read getting format and rate from client"); } configure_audio(scrub(buf[:ESD_FMT_LEN]), scrub(buf[ESD_FMT_LEN:ESD_FMT_LEN+ESD_RATE_LEN])); # Read and discard name. Don't support Esd sample playing etc. if (sys->read(rdfd, buf, ESD_NAME_LEN) != ESD_NAME_LEN) { error("Runt read getting stream name from client"); } #afd := sys->open("/tmp/junk.data", Sys->OWRITE); afd := sys->open("/dev/audio", Sys->OWRITE); if (afd == nil) { error(sys->sprint( "Could not open /dev/audio for writing: %r")); } # # Read actual sound data and write to audio device. # Writes are done in units smaller than ATOMICIO due # to some strange things occuring on large writes. # Not yet sure who's the culprit. # chunklen := sys->read(rdfd, buf, 1024); while(chunklen != 0) { if (sys->write(afd, buf[:chunklen], chunklen) != chunklen) { error(sys->sprint( "Could not write %d bytes to /dev/audio: %r", chunklen)); } debugprint(sys->sprint("wrote %d bytes to /dev/audio", chunklen)); chunklen = sys->read(rdfd, buf, 1024); } } else { sys->sprint("Unknown opcode from client, %d%d%d%d", int buf[0], int buf[1], int buf[2], int buf[3]); } return; } # Scrub() rearranges the byte order. Scrub is used # both taking the return value, or knowing it acts # on array reference. scrub(data : array of byte) : array of byte { if (client_endian == SAME) { return data; } for (i := 0; i < len data/2; i++) { tmp1 := int data[i]; tmp2 := int data[len data - i - 1]; sys->print("\n\n%d --> %d\n", tmp1, tmp2); data[i] = byte tmp2; data[len data - i - 1] = byte tmp1; } return data; } configure_audio(fmt : array of byte, rate: array of byte) { debugprint("ignoring config info, hope you don't mind"); } debugprint(s: string) { if (debug) { sys->print("Esd::DEBUG %s\n", s); } return; } usage() { sys->print("Usage:\nesd [-p port]\n\n"); return; } error(s: string) { sys->raise("fail: Esd:: "+s); exit; }