Plan 9 from Bell Labs’s /usr/web/sources/patch/applied/usbaudio-imic/usbaudioctl.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <thread.h>
#include "usb.h"
#include "usbaudio.h"
#include "usbaudioctl.h"

int endpt[2] =		{-1, -1};
int interface[2] =	{-1, -1};
int featureid[2] =	{-1, -1};
int selectorid[2] =	{-1, -1};
int mixerid[2] =	{-1, -1};
int curalt[2] =		{-1, -1};
int buttonendpt =	-1;

int id;
Device *ad;

Audiocontrol controls[2][Ncontrol] = {
	{
	[Speed_control] = {		"speed",	0, {0}, 0,	44100,	Undef},
	[Mute_control] = {		"mute",		0, {0}, 0,	0,	Undef},
	[Volume_control] = {		"volume",	0, {0}, 0,	0,	Undef},
	[Bass_control] = {		"bass",		0, {0}, 0,	0,	Undef},
	[Mid_control] = {		"mid",		0, {0}, 0,	0,	Undef},
	[Treble_control] = {		"treble",	0, {0}, 0,	0,	Undef},
	[Equalizer_control] = {		"equalizer",	0, {0}, 0,	0,	Undef},
	[Agc_control] = {		"agc",		0, {0}, 0,	0,	Undef},
	[Delay_control] = {		"delay",	0, {0}, 0,	0,	Undef},
	[Bassboost_control] = {		"bassboost",	0, {0}, 0,	0,	Undef},
	[Loudness_control] = {		"loudness",	0, {0}, 0,	0,	Undef},
	[Channel_control] = {		"channels",	0, {0}, 0,	2,	Undef},
	[Resolution_control] = {	"resolution",	0, {0}, 0,	16,	Undef},
//	[Selector_control] = {		"selector",	0, {0}, 0,	0,	Undef},
	}, {
	[Speed_control] = {		"speed",	0, {0}, 0,	44100,	Undef},
	[Mute_control] = {		"mute",		0, {0}, 0,	0,	Undef},
	[Volume_control] = {		"volume",	0, {0}, 0,	0,	Undef},
	[Bass_control] = {		"bass",		0, {0}, 0,	0,	Undef},
	[Mid_control] = {		"mid",		0, {0}, 0,	0,	Undef},
	[Treble_control] = {		"treble",	0, {0}, 0,	0,	Undef},
	[Equalizer_control] = {		"equalizer",	0, {0}, 0,	0,	Undef},
	[Agc_control] = {		"agc",		0, {0}, 0,	0,	Undef},
	[Delay_control] = {		"delay",	0, {0}, 0,	0,	Undef},
	[Bassboost_control] = {		"bassboost",	0, {0}, 0,	0,	Undef},
	[Loudness_control] = {		"loudness",	0, {0}, 0,	0,	Undef},
	[Channel_control] = {		"channels",	0, {0}, 0,	2,	Undef},
	[Resolution_control] = {	"resolution",	0, {0}, 0,	16,	Undef},
//	[Selector_control] = {		"selector",	0, {0}, 0,	0,	Undef},
	}
};

int
setaudioalt(int rec, Audiocontrol *c, int control)
{
	if (debug & Dbgcontrol)
		fprint(2, "setcontrol %s: Set alt %d\n", c->name, control);
	curalt[rec] = control;
	if (setupcmd(ad->ep[0], RH2D|Rstandard|Rinterface, SET_INTERFACE, control, interface[rec], nil, 0) < 0){
		if (debug & Dbgcontrol) fprint(2, "setcontrol: setupcmd %s failed\n", c->name);
			return -1;
	}
	return control;
}

int
findalt(int rec, int nchan, int res, int speed)
{
	Endpt *ep;
	Audioalt *a;
	Dalt *da;
	int i, j, k, retval;

	retval = -1;
	controls[rec][Channel_control].min = 1000000;
	controls[rec][Channel_control].max = 0;
	controls[rec][Channel_control].step = Undef;
	controls[rec][Resolution_control].min = 1000000;
	controls[rec][Resolution_control].max = 0;
	controls[rec][Resolution_control].step = Undef;
	for (i = 0; i < Nendpt; i++) {
		if ((ep = ad->ep[i]) == nil)
			continue;
		if(ep->csp != CSP(CL_AUDIO, 2, 0))
			continue;
		if (ep->iface == nil) {
			fprint(2, "\tno interface\n");
			return 0;
		}
		if ((rec == Play && (ep->addr &  0x80))
		|| (rec == Record && (ep->addr &  0x80) == 0))
			continue;
		for (j = 0; j < 16; j++) {
			if ((da = ep->iface->dalt[j]) == nil || (a = da->devspec) == nil)
				continue;
			if (a->nchan < controls[rec][Channel_control].min)
				controls[rec][Channel_control].min = a->nchan;
			if (a->nchan > controls[rec][Channel_control].max)
				controls[rec][Channel_control].max = a->nchan;
			if (a->res < controls[rec][Resolution_control].min)
				controls[rec][Resolution_control].min = a->res;
			if (a->res > controls[rec][Resolution_control].max)
				controls[rec][Resolution_control].max = a->res;
			controls[rec][Channel_control].settable = 1;
			controls[rec][Channel_control].readable = 1;
			controls[rec][Resolution_control].settable = 1;
			controls[rec][Resolution_control].readable = 1;
			controls[rec][Speed_control].settable = 1;
			controls[rec][Speed_control].readable = 1;
			if (a->nchan == nchan && a->res == res){
				if(speed == Undef)
					retval = j;
				else if(a->caps & (has_discfreq|onefreq)){
					for(k = 0; k < nelem(a->freqs); k++){
						if(a->freqs[k] == speed){
							retval = j;
							break;
						}
					}
				} else {
					if(speed >= a->minfreq && speed <= a->maxfreq)
						retval = j;
				}
			}
		}
	}
	if ((debug & Dbgcontrol) && retval < 0){
		fprint(2, "findalt(%d, %d, %d, %d) failed\n", rec, nchan, res, speed);
	}
	return retval;
}

int
setspeed(int rec, int speed)
{
	int ps, n, no, dist, i;
	Audioalt *a;
	Dalt *da;
	Endpt *ep;
	char cmdbuf[32];
	uchar buf[3];

	if (curalt[rec] < 0){
		fprint(2, "Must set channels and resolution before speed\n");
		return Undef;
	}
	if (endpt[rec] < 0)
		sysfatal("endpt[%s] not set\n", rec?"Record":"Playback");
	ep = ad->ep[endpt[rec]];
	if (ep->iface == nil)
		sysfatal("no interface");
	if (curalt[rec] < 0)
		sysfatal("curalt[%s] not set\n", rec?"Record":"Playback");
	da = ep->iface->dalt[curalt[rec]];
	a = da->devspec;
	if (a->caps & onefreq){
		if (debug & Dbgcontrol)
			fprint(2, "setspeed %d: onefreq\n", speed);
		speed = a->freqs[0];		/* speed not settable, but packet size must still be set */
	}else if (a->caps & has_contfreq){
		if (debug & Dbgcontrol)
			fprint(2, "setspeed %d: contfreq\n", speed);
		if (speed < a->minfreq)
			speed = a->minfreq;
		else if (speed > a->maxfreq)
			speed = a->maxfreq;
		if (debug & Dbgcontrol)
			fprint(2, "Setting continuously variable %s speed to %d\n",
				rec?"record":"playback", speed);
	}else if (a->caps & has_discfreq){
		if (debug & Dbgcontrol)
			fprint(2, "setspeed %d: discfreq\n", speed);
		dist = 1000000;
		no = -1;
		for (i = 0; a->freqs[i] > 0; i++)
			if (abs(a->freqs[i] - speed) < dist){
				dist = abs(a->freqs[i] - speed);
				no = i;
			}
		if (no == -1){
			if (debug & Dbgcontrol)
				fprint(2, "no = -1\n");
			return Undef;
		}
		speed = a->freqs[no];
		if (debug & Dbgcontrol)
			fprint(2, "Setting discreetly variable %s speed to %d\n",
				rec?"record":"playback", speed);
	}else{
		if (debug & Dbgcontrol)
			fprint(2, "can't happen\n?");
		return Undef;
	}
	if (a->caps & has_setspeed){
		if (debug & Dbgcontrol)
			fprint(2, "Setting %s speed to %d Hz;", rec?"record":"playback", speed);
		buf[0] = speed;
		buf[1] = speed >> 8;
		buf[2] = speed >> 16;
		n = endpt[rec];
		if (rec)
			n |= 0x80;
		if(setupcmd(ad->ep[0], RH2D|Rclass|Rendpt, SET_CUR, sampling_freq_control<<8, n, buf, 3) < 0){
			fprint(2, "Error in setupcmd\n");
			return Undef;
		}
		if (setupreq(ad->ep[0], RD2H|Rclass|Rendpt, GET_CUR, sampling_freq_control<<8, n, 3) < 0){
			fprint(2, "Error in setupreq\n");
			return Undef;
		}
		n = setupreply(ad->ep[0], buf, 3);
		if (n != 3)
			fprint(2, "Error in setupreply: %d\n", n);
		else{
			n = buf[0] | buf[1] << 8 | buf[2] << 16;
			if (buf[2] || n == 0){
				if (debug & Dbgcontrol)
					fprint(2, "Speed out of bounds %d (0x%x)\n", n, n);
			}else if (n != speed && ad->vid == 0x077d && (ad->did == 0x0223 || ad->did == 0x07af)){
				/* Griffin iMic responds incorrectly to sample rate inquiry */
				if (debug & Dbgcontrol)
					fprint(2, " reported as %d (iMic bug?);", n);
			}else
				speed = n;
		}
		if (debug & Dbgcontrol)
			fprint(2, " speed now %d Hz;", speed);
	}
	ps = ((speed * da->interval + 999) / 1000)
		* controls[rec][Channel_control].value[0]
		* controls[rec][Resolution_control].value[0]/8;
	if(ps > ep->maxpkt){
		fprint(2, "packet size %d > maximum packet size %d\n",
			ps, ep->maxpkt);
		return Undef;
	}
	if (debug & Dbgcontrol)
		fprint(2, "Configuring %s endpoint for %d Hz\n",
				rec?"record":"playback", speed);
	sprint(cmdbuf, "ep %d %d %c %ld %d", endpt[rec], da->interval, rec?'r':'w', 
		controls[rec][Channel_control].value[0]*controls[rec][Resolution_control].value[0]/8,
		speed);
	if (write(ad->ctl, cmdbuf, strlen(cmdbuf)) != strlen(cmdbuf)){
		fprint(2, "writing %s to #U/usb/%d/ctl: %r\n", cmdbuf, id);
		return Undef;
	}
	if (debug & Dbgcontrol) fprint(2, "sent `%s' to /dev/usb/%d/ctl\n", cmdbuf, id);
	return speed;
}

long
getspeed(int rec, int which)
{
	int i, n;
	Audioalt *a;
	Dalt *da;
	Endpt *ep;
	uchar buf[3];

	if (curalt[rec] < 0){
		fprint(2, "Must set channels and resolution before getspeed\n");
		return Undef;
	}
	if (endpt[rec] < 0)
		sysfatal("endpt[%s] not set\n", rec?"Record":"Playback");
	if(debug & Dbgcontrol)
		fprint(2, "getspeed: endpt[%d] == %d\n", rec, endpt[rec]);
	ep = ad->ep[endpt[rec]];
	if (ep->iface == nil)
		sysfatal("no interface");
	if (curalt[rec] < 0)
		sysfatal("curalt[%s] not set\n", rec?"Record":"Playback");
	da = ep->iface->dalt[curalt[rec]];
	a = da->devspec;
	if (a->caps & onefreq){
		if(debug & Dbgcontrol)
			fprint(2, "getspeed: onefreq\n");
		if (which == GET_RES)
			return Undef;
		return a->freqs[0];		/* speed not settable */
	}
	if (a->caps & has_setspeed){
		if(debug & Dbgcontrol)
			fprint(2, "getspeed: has_setspeed, ask\n");
		n = endpt[rec];
		if (rec)
			n |= 0x80;
		if (setupreq(ad->ep[0], RD2H|Rclass|Rendpt, which, sampling_freq_control<<8, n, 3) < 0)
			return Undef;
		n = setupreply(ad->ep[0], buf, 3);
		if(n == 3){
			if(buf[2]){
				if (debug & Dbgcontrol)
					fprint(2, "Speed out of bounds\n");
				if ((a->caps & has_discfreq) && (buf[0] | buf[1] << 8) < 8)
					return a->freqs[buf[0] | buf[1] << 8];
			}
			return buf[0] | buf[1] << 8 | buf[2] << 16;
		}
		if(debug & Dbgcontrol)
			fprint(2, "getspeed: n = %d\n", n);
	}
	if (a->caps & has_contfreq){
		if(debug & Dbgcontrol)
			fprint(2, "getspeed: has_contfreq\n");
		if (which == GET_CUR)
			return controls[rec][Speed_control].value[0];
		if (which == GET_MIN)
			return a->minfreq;
		if (which == GET_MAX)
			return a->maxfreq;
		if (which == GET_RES)
			return 1;
	}
	if (a->caps & has_discfreq){
		if(debug & Dbgcontrol)
			fprint(2, "getspeed: has_discfreq\n");
		if (which == GET_CUR)
			return controls[rec][Speed_control].value[0];
		if (which == GET_MIN)
			return a->freqs[0];
		for (i = 0; i < 8 && a->freqs[i] > 0; i++)
			;
		if (which == GET_MAX)
			return a->freqs[i-1];
		if (which == GET_RES)
			return Undef;
	}
	if (debug & Dbgcontrol)
		fprint(2, "can't happen\n?");
	return Undef;
}

int
setcontrol(int rec, char *name, long *value)
{
	int i, ctl, m;
	byte buf[3];
	int type, req, control, index, count;
	Audiocontrol *c;

	c = nil;
	for (ctl = 0; ctl < Ncontrol; ctl++){
		c = &controls[rec][ctl];
		if (strcmp(name, c->name) == 0)
			break;
	}
	if (ctl == Ncontrol){
		if (debug & Dbgcontrol) fprint(2, "setcontrol: control not found\n");
		return -1;
	}
	if (c->settable == 0) {
		if (debug & Dbgcontrol) fprint(2, "setcontrol: control %d.%d not settable\n", rec, ctl);
		if (c->chans){
			for (i = 0; i < 8; i++)
				if ((c->chans & 1 << i) && c->value[i] != value[i])
					return -1;
			return 0;
		}
		if (c->value[0] != value[0])
			return -1;
		return 0;
	}
	if (c->chans){
		value[0] = 0;	// set to average
		m = 0;
		for (i = 1; i < 8; i++)
			if (c->chans & 1 << i){
				if (c->min != Undef && value[i] < c->min)
					value[i] = c->min;
				if (c->max != Undef && value[i] > c->max)
					value[i] = c->max;
				value[0] += value[i];
				m++;
			} else
				value[i] = Undef;
		if (m) value[0] /= m;
	}else{
		if (c->min != Undef && value[0] < c->min)
			value[0] = c->min;
		if (c->max != Undef && value[0] > c->max)
			value[0] = c->max;
	}
	req = SET_CUR;
	count = 1;
	switch(ctl){
	default:
		if (debug & Dbgcontrol) fprint(2, "setcontrol: can't happen\n");
		return -1;
	case Speed_control:
		if ((value[0] = setspeed(rec, value[0])) < 0)
			return -1;
		c->value[0] = value[0];
		return 0;
	case Equalizer_control:
		/* not implemented */
		return -1;
	case Resolution_control:
		control = findalt(rec, controls[rec][Channel_control].value[0], value[0], defaultspeed[rec]);
		if(control < 0 || setaudioalt(rec, c, control) < 0){
			if (debug & Dbgcontrol) fprint(2, "setcontrol: can't find setting for %s\n",
				c->name);
			return -1;
		}
		c->value[0] = value[0];
		controls[rec][Speed_control].value[0] = defaultspeed[rec];
		return 0;
	case Volume_control:
	case Delay_control:
		count = 2;
		/* fall through */
	case Mute_control:
	case Bass_control:
	case Mid_control:
	case Treble_control:
	case Agc_control:
	case Bassboost_control:
	case Loudness_control:
		type = RH2D|Rclass|Rinterface;
		control = ctl<<8;
		index = featureid[rec]<<8;
		break;
	case Selector_control:
		type = RH2D|Rclass|Rinterface;
		control = 0;
		index = selectorid[rec]<<8;
		break;
	case Channel_control:
		control = findalt(rec, value[0], controls[rec][Resolution_control].value[0], defaultspeed[rec]);
		if(control < 0 || setaudioalt(rec, c, control) < 0){
			if (debug & Dbgcontrol) fprint(2, "setcontrol: can't find setting for %s\n",
				c->name);
			return -1;
		}
		c->value[0] = value[0];
		controls[rec][Speed_control].value[0] = defaultspeed[rec];
		return 0;
	}
	if(c->chans){
		for (i = 1; i < 8; i++)
			if (c->chans & 1 << i){
				switch(count){
				case 2:
					buf[1] = value[i] >> 8;
				case 1:
					buf[0] = value[i];
				}
				if (setupcmd(ad->ep[0], type, req, control | i, index, buf, count) < 0){
					if (debug & Dbgcontrol) fprint(2, "setcontrol: setupcmd %s failed\n",
						controls[rec][ctl].name);
					return -1;
				}
				c->value[i] = value[i];
			}
	}else{
		switch(count){
		case 2:
			buf[1] = value[0] >> 8;
		case 1:
			buf[0] = value[0];
		}
		if (setupcmd(ad->ep[0], type, req, control, index, buf, count) < 0){
			if (debug & Dbgcontrol) fprint(2, "setcontrol: setupcmd %s failed\n",
				c->name);
			return -1;
		}
	}
	c->value[0] = value[0];
	return 0;
}

int
getspecialcontrol(int rec, int ctl, int req, long *value)
{
	byte buf[3];
	int m, n, i;
	int type, control, index, count, signedbyte;
	short svalue;

	count = 1;
	signedbyte = 0;
	switch(ctl){
	default:
		return Undef;
	case Speed_control:
		value[0] =  getspeed(rec, req);
		return 0;
	case Channel_control:
	case Resolution_control:
		if (req == GET_MIN)
			value[0] = controls[rec][ctl].min;
		if (req == GET_MAX)
			value[0] = controls[rec][ctl].max;
		if (req == GET_RES)
			value[0] = controls[rec][ctl].step;
		if (req == GET_CUR)
			value[0] = controls[rec][ctl].value[0];
		return 0;
	case Volume_control:
	case Delay_control:
		count = 2;
		/* fall through */
	case Bass_control:
	case Mid_control:
	case Treble_control:
	case Equalizer_control:
		signedbyte = 1;
		type = RD2H|Rclass|Rinterface;
		control = ctl<<8;
		index = featureid[rec]<<8;
		break;
	case Selector_control:
		type = RD2H|Rclass|Rinterface;
		control = 0;
		index = selectorid[rec]<<8;
		break;
	case Mute_control:
	case Agc_control:
	case Bassboost_control:
	case Loudness_control:
		if (req != GET_CUR)
			return Undef;
		type = RD2H|Rclass|Rinterface;
		control = ctl<<8;
		index = featureid[rec]<<8;
		break;
	}
	if (controls[rec][ctl].chans){
		m = 0;
		value[0] = 0; // set to average
		for (i = 1; i < 8; i++){
			value[i] = Undef;
			if (controls[rec][ctl].chans & 1 << i){
				if (setupreq(ad->ep[0], type, req, control | i, index, count) < 0)
					return Undef;
				n = setupreply(ad->ep[0], buf, count);
				if (n != count)
					return -1;
				switch (count) {
				case 2:
					svalue = buf[1] << 8 | buf[0];
					if (req == GET_CUR){
						value[i] = svalue;
						value[0] += svalue;
						m++;
					}else
						value[0] = svalue;
					break;
				case 1:
					svalue = buf[0];
					if (signedbyte && (svalue&0x80))
						svalue |= 0xFF00;
					if (req == GET_CUR){
						value[i] = svalue;
						value[0] += svalue;
						m++;
					}else
						value[0] = svalue;
				}
			}
		}
		if (m) value[0] /= m;
		return 0;
	}
	value[0] = Undef;
	if (setupreq(ad->ep[0], type, req, control, index, count) < 0)
		return -1;
	n = setupreply(ad->ep[0], buf, count);
	if (n != count)
		return -1;
	switch (count) {
	case 2:
		svalue = buf[1] << 8 | buf[0];
		value[0] = svalue;
		break;
	case 1:
		svalue = buf[0];
		if (signedbyte && (svalue&0x80))
			svalue |= 0xFF00;
		value[0] = svalue;
	}
	return 0;
}

int
getcontrol(int rec, char *name, long *value)
{
	int i;

	for (i = 0; i < Ncontrol; i++){
		if (strcmp(name, controls[rec][i].name) == 0)
			break;
	}
	if (i == Ncontrol)
		return -1;
	if (controls[rec][i].readable == 0)
		return -1;
	if(getspecialcontrol(rec, i, GET_CUR, value) < 0)
		return -1;
	memmove(controls[rec][i].value, value, sizeof controls[rec][i].value);
	return 0;
}

void
getcontrols(void)
{
	int rec, ctl, i;
	Audiocontrol *c;
	long v[8];

	for (rec = 0; rec < 2; rec++)
		for (ctl = 0; ctl < Ncontrol; ctl++){
			c = &controls[rec][ctl];
			if (c->readable){
				if (verbose)
					fprint(2, "%s %s control",
						rec?"Record":"Playback", controls[rec][ctl].name);
				c->min = (getspecialcontrol(rec, ctl, GET_MIN, v) < 0) ? Undef : v[0];
				if (verbose && c->min != Undef)
					fprint(2, ", min %ld", c->min);
				c->max = (getspecialcontrol(rec, ctl, GET_MAX, v) < 0) ? Undef : v[0];
				if (verbose && c->max != Undef)
					fprint(2, ", max %ld", c->max);
				c->step = (getspecialcontrol(rec, ctl, GET_RES, v) < 0) ? Undef : v[0];
				if (verbose && c->step != Undef)
					fprint(2, ", step %ld", c->step);
				if (getspecialcontrol(rec, ctl, GET_CUR, c->value) == 0){
					if (verbose) {
						if (c->chans){
							fprint(2, ", values");
							for (i = 1; i < 8; i++)
								if (c->chans & 1 << i)
									fprint(2, "[%d] %ld  ", i, c->value[i]);
						}else
							fprint(2, ", value %ld", c->value[0]);
					}
				}
				if (verbose)
					fprint(2, "\n");
			} else {
				c->min = Undef;
				c->max = Undef;
				c->step = Undef;
				c->value[0] = Undef;
				if (debug & Dbgcontrol)
					fprint(2, "%s %s control not settable\n",
						rec?"Playback":"Record", controls[rec][ctl].name);
			}
		}
}

int
ctlparse(char *s, Audiocontrol *c, long *v)
{
	int i, j, nf, m;
	char *vals[9];
	char *p;
	long val;

	nf = tokenize(s, vals, nelem(vals));
	if (nf <= 0)
		return -1;
	if (c->chans){
		j = 0;
		m = 0;
		SET(val);
		v[0] = 0;	// will compute average of v[i]
		for (i = 1; i < 8; i++)
			if (c->chans & 1 << i) {
				if (j < nf){
					val = strtol(vals[j], &p, 0);
					if (val == 0 && *p != '\0' && *p != '%')
						return -1;
					if (*p == '%' && c->min != Undef)
						val = (val*c->max + (100-val)*c->min)/100;
					j++;
				}
				v[i] = val;
				v[0] += val;
				m++;
			} else
				v[i] = Undef;
		if (m) v[0] /= m;
	} else {
		val = strtol(vals[0], &p, 0);
		if (*p == '%' && c->min != Undef)
			val = (val*c->max + (100-val)*c->min)/100;
		v[0] = val;
	}
	return 0;
}

int
Aconv(Fmt *fp)
{
	char str[256];
	Audiocontrol *c;
	int fst, i;
	char *p;

	c = va_arg(fp->args, Audiocontrol*);
	p = str;
	if (c->chans) {
		fst = 1;
		for (i = 1; i < 8; i++)
			if (c->chans & 1 << i){
				p = seprint(p, str+sizeof str, "%s%ld", fst?"'":" ", c->value[i]);
				fst = 0;
			}
		seprint(p, str+sizeof str, "'");
	} else
		seprint(p, str+sizeof str, "%ld", c->value[0]);
	return fmtstrcpy(fp, str);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.