## ESD Audio device. Serves /dev/audio and /dev/audioctl, and# sends received audio to an Esound daemon, either local or# somewhere on the network. Esound is the Enlightenment sound# "thingy", and is available as a server application on many# Unices. This program negotiates the ESD protocol etc.## /dev/audio accepts audio data. /dev/audioctl accepts a # restricted set of the usual Inferno audio header commands:## rate n# Where n is the sampling rate. E.g., 44100,# 22050, 8000, etc.## chans n# Where n is'1' for mono and '2' for stereo## bits n# Bits per sample. Valid values for n are '8' # and '16'## enc e# Where e is the encoding method. This is # currently not supported.## The device defaults to 16 bit samples, 44.1KHz, stereo## BUGS: we should mimic /dev/audioctl exactly.## This software copyright 2003 Phillip Stanley-Marbell# Contact: pip@gemusehaken.org, pstanley@ece.cmu.edu#implement EsdAudioDev; include "sys.m";include "draw.m";include "arg.m";include "esd.m"; EsdAudioDev : module{ init : fn(nil : ref Draw->Context, nil : list of string);}; sys : Sys;arg : Arg; # Defaultshostname := "localhost";port := 16001;binddir := "/dev";debug := 1;current_format : array of byte;current_rate : array of byte;current_name : array of byte;current_chans := 2;current_bits := 16; pgrp : int;esdresetchan : chan of int; init(nil : ref Draw->Context, args : list of string){ sys = load Sys Sys->PATH; arg = load Arg Arg->PATH; if (arg == nil) { sys->raise(sys->sprint("Could not load %s : %r", Arg->PATH)); } arg->init(args); pgrp = sys->pctl(sys->NEWPGRP, nil); while((c := arg->opt()) != 0) { case c { 'p' => port = int arg->arg(); 'd' => binddir = string arg->arg(); 'h' => hostname = string arg->arg(); * => usage(); exit; } } current_format = FMT_16BIT_STEREO_STREAM; current_rate = RATE_44100; current_name = ESD_NAME; sys->bind("#s", binddir, sys->MBEFORE); datachanref := sys->file2chan(binddir, "audio"); ctlchanref := sys->file2chan(binddir, "audioctl"); if (datachanref == nil || ctlchanref == nil) { sys->print("EsdAudioDev:: Couldn't create chan file in %s : %r\n", binddir); exit; } spawn dataworker(datachanref); spawn ctlworker(ctlchanref); return;} dataworker(chanref : ref sys->FileIO){ esddatachan := chan of array of byte; esdresetchan = chan of int; spawn esdconnect(esddatachan, esdresetchan); while (1) alt { # For now, ignore reads (off, nbytes, fid, rc) := <-chanref.read => { if (rc == nil) break; # Finished serving contents of data[] rc <-= (nil, ""); } (offset, writedata, fid, wc) := <-chanref.write => { if (wc == nil) { break; } esddatachan <- = writedata; wc <-= (len writedata, ""); } }} ctlworker(chanref : ref sys->FileIO){ index := 0; data : array of byte; while (1) alt { (off, nbytes, fid, rc) := <-chanref.read => { if (rc == nil) break; if (index == 0) { data = array of byte ("rate "+string current_rate+"\n"+ "chans "+string current_chans+"\n"+ "bits "+string current_bits+"\n"); } if (index < len data) { end := min(index+nbytes, len data); rc <-= (data[index:end], ""); index = end; } else { # Finished serving contents of data[] rc <-= (nil, ""); index = 0; } } (offset, writedata, fid, wc) := <-chanref.write => { if (wc == nil) { break; } wc <-= (len writedata, ""); debugprint(string writedata); if (string writedata[:len "quit"] == "quit") { fd := sys->open("#p/"+string pgrp+"/ctl", sys->OWRITE); if (fd != nil) { sys->fprint(fd, "killgrp"); # Killgrp into ctl doesn't cause suicide, so: exit; } else { sys->raise("fail: EsdAudioDev: could not terminate"); } } if (string writedata[:len "rate"] == "rate") { (nil, l) := sys->tokenize(string writedata, " "); rate := hd tl l; if (int rate <= 0) { error("bogus sampling rate supplied"); continue; } debugprint(sys->sprint("setting sampling rate to [%d]", int rate)); current_rate = array [] of {byte ((int rate>>24)&16rFF), byte ((int rate>>16)&16rFF), byte ((int rate>>8)&16rFF), byte ((int rate>>0)&16rFF)}; esdresetchan <-= 1; } if (string writedata[:len "chans"] == "chans") { (nil, l) := sys->tokenize(string writedata, " "); chans := hd tl l; if (int chans < 1 || int chans > 2) { error("bogus # of channels supplied"); continue; } debugprint(sys->sprint("setting sampling chans to [%d]", int chans)); current_chans = int chans; reset_format(); esdresetchan <-= 1; } if (string writedata[:len "bits"] == "bits") { (nil, l) := sys->tokenize(string writedata, " "); bits := hd tl l; if (int bits != 8 && int bits != 16) { error("bogus sample #bits supplied"); continue; } debugprint(sys->sprint("setting sampling #bits to [%d]", int bits)); current_bits = int bits; reset_format(); esdresetchan <-= 1; } } }} esdconnect(esddatachan : chan of array of byte, esdresetchan : chan of int){ reply := array [ESD_OPCODE_LEN] of byte; n := 0; (ok, conn) := sys->dial("tcp!"+hostname+"!"+string port, nil); if (ok < 0) { error("error: could not connect to server"); } debugprint(sys->sprint("connected to host %s...", hostname)); # # Send initial connect opcode. This seems to be valid only# # for JEsd and not for the regular Esd:# n = sys->write(conn.dfd, OPCODE_INIT, ESD_OPCODE_LEN);# if (n != ESD_OPCODE_LEN)# {# error("could not write OPCODE_INIT");# }# debugprint(sys->sprint("wrote [%d] bytes to server", n)); # Send ESD key n = sys->write(conn.dfd, ESD_KEY, ESD_KEY_LEN); if (n != ESD_KEY_LEN) { error("could not write ESD_KEY"); } debugprint(sys->sprint("wrote [%d] bytes to server", n)); # Send Endianness n = sys->write(conn.dfd, ESD_ENDIAN, ESD_ENDIAN_LEN); if (n != ESD_ENDIAN_LEN) { error("could not write ESD_ENDIAN"); } debugprint(sys->sprint("wrote [%d] bytes to server", n)); debugprint("sent initial init messages. about to read response...."); # # Get connect response# n = sys->read(conn.dfd, reply, len reply);## if (int reply[0] != 1)# {# error("server responded negatively on init sequence");# }# debugprint(# sys->sprint(# "server responded with [%d] bytes, value of first byte is [%d]",# n, int reply[0])); # Send any received data as a stream debugprint("about to loop on receive stream / write to server..."); while (1) alt { data := <- esddatachan => n = sys->write(conn.dfd, OPCODE_STREAM, ESD_OPCODE_LEN); if (n != ESD_OPCODE_LEN) { error("could not write OPCODE_STREAM"); } n = sys->write(conn.dfd, current_format, len current_format); if (n != len current_format) { error("could not write current_format"); } n = sys->write(conn.dfd, current_rate, len current_rate); if (n != len current_rate) { error("could not write current_rate"); } n = sys->write(conn.dfd, current_name, len current_name); if (n != len current_name) { error("could not write current_name"); } n = sys->write(conn.dfd, data, len data); if (n != len data) { error("could not write data read from channel"); } <- esdresetchan => reply = nil; conn.dfd = nil; esdconnect(esddatachan, esdresetchan); }} reset_format(){ if (current_chans == 1) { if (current_bits == 8) { current_format = FMT_8BIT_MONO_STREAM; } else if (current_bits = 16) { current_format = FMT_16BIT_MONO_STREAM; } } else if (current_chans == 2) { if (current_bits == 8) { current_format = FMT_8BIT_STEREO_STREAM; } else if (current_bits = 16) { current_format = FMT_16BIT_STEREO_STREAM; } } return;} error(err: string){ fd := sys->open(binddir+"/ctl", sys->OWRITE); if (fd != nil) { sys->fprint(fd, "error: %s", err); } else { sys->raise("fail: EsdAudioDev: could not write to ctl file"); } return;} debugprint(s: string){ if (debug) { sys->print("EsdAudioDev::DEBUG %s\n", s); } return;} min (a, b : int) : int{ if (a < b) return a; return b;} usage(){ sys->print("Udsage:\nesdaudiodev [-p port] [-d directory] [-h hostname]\n\n"); return;}
#
# ESD Audio device. Serves /dev/audio and /dev/audioctl, and
# sends received audio to an Esound daemon, either local or
# somewhere on the network. Esound is the Enlightenment sound
# "thingy", and is available as a server application on many
# Unices. This program negotiates the ESD protocol etc.
#
# /dev/audio accepts audio data. /dev/audioctl accepts a
# restricted set of the usual Inferno audio header commands:
#
# rate n
# Where n is the sampling rate. E.g., 44100,
# 22050, 8000, etc.
#
# chans n
# Where n is'1' for mono and '2' for stereo
#
# bits n
# Bits per sample. Valid values for n are '8'
# and '16'
#
# enc e
# Where e is the encoding method. This is
# currently not supported.
#
# The device defaults to 16 bit samples, 44.1KHz, stereo
#
# BUGS: we should mimic /dev/audioctl exactly.
#
# This software copyright 2003 Phillip Stanley-Marbell
# Contact: pip@gemusehaken.org, pstanley@ece.cmu.edu
#
implement EsdAudioDev;
include "sys.m";
include "draw.m";
include "arg.m";
include "esd.m";
EsdAudioDev : module
{
init : fn(nil : ref Draw->Context, nil : list of string);
};
sys : Sys;
arg : Arg;
# Defaults
hostname := "localhost";
port := 16001;
binddir := "/dev";
debug := 1;
current_format : array of byte;
current_rate : array of byte;
current_name : array of byte;
current_chans := 2;
current_bits := 16;
pgrp : int;
esdresetchan : chan of int;
init(nil : ref Draw->Context, args : list of string)
{
sys = load Sys Sys->PATH;
arg = load Arg Arg->PATH;
if (arg == nil)
{
sys->raise(sys->sprint("Could not load %s : %r", Arg->PATH));
}
arg->init(args);
pgrp = sys->pctl(sys->NEWPGRP, nil);
while((c := arg->opt()) != 0)
{
case c
{
'p' => port = int arg->arg();
'd' => binddir = string arg->arg();
'h' => hostname = string arg->arg();
* =>
usage();
exit;
}
}
current_format = FMT_16BIT_STEREO_STREAM;
current_rate = RATE_44100;
current_name = ESD_NAME;
sys->bind("#s", binddir, sys->MBEFORE);
datachanref := sys->file2chan(binddir, "audio");
ctlchanref := sys->file2chan(binddir, "audioctl");
if (datachanref == nil || ctlchanref == nil)
{
sys->print("EsdAudioDev:: Couldn't create chan file in %s : %r\n",
binddir);
exit;
}
spawn dataworker(datachanref);
spawn ctlworker(ctlchanref);
return;
}
dataworker(chanref : ref sys->FileIO)
{
esddatachan := chan of array of byte;
esdresetchan = chan of int;
spawn esdconnect(esddatachan, esdresetchan);
while (1)
alt
{
# For now, ignore reads
(off, nbytes, fid, rc) := <-chanref.read =>
{
if (rc == nil) break;
# Finished serving contents of data[]
rc <-= (nil, "");
}
(offset, writedata, fid, wc) := <-chanref.write =>
{
if (wc == nil)
{
break;
}
esddatachan <- = writedata;
wc <-= (len writedata, "");
}
}
}
ctlworker(chanref : ref sys->FileIO)
{
index := 0;
data : array of byte;
while (1)
alt
{
(off, nbytes, fid, rc) := <-chanref.read =>
{
if (rc == nil) break;
if (index == 0)
{
data = array of byte
("rate "+string current_rate+"\n"+
"chans "+string current_chans+"\n"+
"bits "+string current_bits+"\n");
}
if (index < len data)
{
end := min(index+nbytes, len data);
rc <-= (data[index:end], "");
index = end;
}
else
{
# Finished serving contents of data[]
rc <-= (nil, "");
index = 0;
}
}
(offset, writedata, fid, wc) := <-chanref.write =>
{
if (wc == nil)
{
break;
}
wc <-= (len writedata, "");
debugprint(string writedata);
if (string writedata[:len "quit"] == "quit")
{
fd := sys->open("#p/"+string pgrp+"/ctl",
sys->OWRITE);
if (fd != nil)
{
sys->fprint(fd, "killgrp");
# Killgrp into ctl doesn't cause suicide, so:
exit;
}
else
{
sys->raise("fail: EsdAudioDev: could not terminate");
}
}
if (string writedata[:len "rate"] == "rate")
{
(nil, l) := sys->tokenize(string writedata, " ");
rate := hd tl l;
if (int rate <= 0)
{
error("bogus sampling rate supplied");
continue;
}
debugprint(sys->sprint("setting sampling rate to [%d]",
int rate));
current_rate = array [] of {byte ((int rate>>24)&16rFF),
byte ((int rate>>16)&16rFF),
byte ((int rate>>8)&16rFF),
byte ((int rate>>0)&16rFF)};
esdresetchan <-= 1;
}
if (string writedata[:len "chans"] == "chans")
{
(nil, l) := sys->tokenize(string writedata, " ");
chans := hd tl l;
if (int chans < 1 || int chans > 2)
{
error("bogus # of channels supplied");
continue;
}
debugprint(sys->sprint("setting sampling chans to [%d]",
int chans));
current_chans = int chans;
reset_format();
esdresetchan <-= 1;
}
if (string writedata[:len "bits"] == "bits")
{
(nil, l) := sys->tokenize(string writedata, " ");
bits := hd tl l;
if (int bits != 8 && int bits != 16)
{
error("bogus sample #bits supplied");
continue;
}
debugprint(sys->sprint("setting sampling #bits to [%d]",
int bits));
current_bits = int bits;
reset_format();
esdresetchan <-= 1;
}
}
}
}
esdconnect(esddatachan : chan of array of byte, esdresetchan : chan of int)
{
reply := array [ESD_OPCODE_LEN] of byte;
n := 0;
(ok, conn) := sys->dial("tcp!"+hostname+"!"+string port, nil);
if (ok < 0)
{
error("error: could not connect to server");
}
debugprint(sys->sprint("connected to host %s...", hostname));
# # Send initial connect opcode. This seems to be valid only
# # for JEsd and not for the regular Esd:
# n = sys->write(conn.dfd, OPCODE_INIT, ESD_OPCODE_LEN);
# if (n != ESD_OPCODE_LEN)
# {
# error("could not write OPCODE_INIT");
# }
# debugprint(sys->sprint("wrote [%d] bytes to server", n));
# Send ESD key
n = sys->write(conn.dfd, ESD_KEY, ESD_KEY_LEN);
if (n != ESD_KEY_LEN)
{
error("could not write ESD_KEY");
}
debugprint(sys->sprint("wrote [%d] bytes to server", n));
# Send Endianness
n = sys->write(conn.dfd, ESD_ENDIAN, ESD_ENDIAN_LEN);
if (n != ESD_ENDIAN_LEN)
{
error("could not write ESD_ENDIAN");
}
debugprint(sys->sprint("wrote [%d] bytes to server", n));
debugprint("sent initial init messages. about to read response....");
# # Get connect response
# n = sys->read(conn.dfd, reply, len reply);
#
# if (int reply[0] != 1)
# {
# error("server responded negatively on init sequence");
# }
# debugprint(
# sys->sprint(
# "server responded with [%d] bytes, value of first byte is [%d]",
# n, int reply[0]));
# Send any received data as a stream
debugprint("about to loop on receive stream / write to server...");
while (1)
alt
{
data := <- esddatachan =>
n = sys->write(conn.dfd, OPCODE_STREAM, ESD_OPCODE_LEN);
if (n != ESD_OPCODE_LEN)
{
error("could not write OPCODE_STREAM");
}
n = sys->write(conn.dfd, current_format, len current_format);
if (n != len current_format)
{
error("could not write current_format");
}
n = sys->write(conn.dfd, current_rate, len current_rate);
if (n != len current_rate)
{
error("could not write current_rate");
}
n = sys->write(conn.dfd, current_name, len current_name);
if (n != len current_name)
{
error("could not write current_name");
}
n = sys->write(conn.dfd, data, len data);
if (n != len data)
{
error("could not write data read from channel");
}
<- esdresetchan =>
reply = nil;
conn.dfd = nil;
esdconnect(esddatachan, esdresetchan);
}
}
reset_format()
{
if (current_chans == 1)
{
if (current_bits == 8)
{
current_format = FMT_8BIT_MONO_STREAM;
}
else if (current_bits = 16)
{
current_format = FMT_16BIT_MONO_STREAM;
}
}
else if (current_chans == 2)
{
if (current_bits == 8)
{
current_format = FMT_8BIT_STEREO_STREAM;
}
else if (current_bits = 16)
{
current_format = FMT_16BIT_STEREO_STREAM;
}
}
return;
}
error(err: string)
{
fd := sys->open(binddir+"/ctl", sys->OWRITE);
if (fd != nil)
{
sys->fprint(fd, "error: %s", err);
}
else
{
sys->raise("fail: EsdAudioDev: could not write to ctl file");
}
return;
}
debugprint(s: string)
{
if (debug)
{
sys->print("EsdAudioDev::DEBUG %s\n", s);
}
return;
}
min (a, b : int) : int
{
if (a < b)
return a;
return b;
}
usage()
{
sys->print("Udsage:\nesdaudiodev [-p port] [-d directory] [-h hostname]\n\n");
return;
}