/*
 * vi:ts=4:sw=4: 
 */
/*
 * Ruby extention library for EsounD
 * (c) Copyright 1999,2001 Hiroshi Kuwagata mailto:kgt@topaz.ocn.ne.jp
 * All rights reserverd.
 *
 * ORIGINAL EsounD (c) Copyright 1998-2001  Eric B. Mitchell (aka 'Ricdude)
 *													 <ericmit@ix.netcom.com>
 */
/*
    Copyright (c) 2000 Hiroshi Kuwagata, japan.
          All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer as
       the first lines of this file unmodified.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY Hiroshi Kuwagata ``AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL Hiroshi Kuwagata BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "ruby.h"
#include <esd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>

#define MAX_HOST_NAME		64
#define MAX_NAME			16

/* EsounD's base structure */
typedef struct {
	int		sock;
	char	host[MAX_HOST_NAME];
	int		default_right;
	int		default_left;
} ESOUND;

typedef struct {
	ESOUND*	parent;
	int		id;
	int		right, left;
	struct timeval	time;
	struct timeval	start;
	char	name[MAX_NAME];
} ESOUND_SAMPLE;

typedef struct {
	int		sock;
	int		id;
	char	host[MAX_HOST_NAME];
	char	name[MAX_NAME];
} ESOUND_STREAM;


/* EsounD's methods */
VALUE rb_esd_open( VALUE self, VALUE host);
void rb_esd_free( ESOUND* body);
VALUE rb_esd_close( VALUE self);
VALUE rb_esd_lock( VALUE self);
VALUE rb_esd_unlock( VALUE self);
VALUE rb_esd_default_pan( VALUE self, VALUE left, VALUE right);
VALUE rb_esd_cache( VALUE self, VALUE fmt, VALUE rate, VALUE data);
VALUE rb_esd_file_cache( VALUE self, VALUE fname);
VALUE rb_esd_play_file( VALUE self, VALUE fname);

/* EsounD::Sample's methods */
VALUE rb_esd_sample_free( VALUE self);
void rb_esd_sample_xfree( ESOUND_SAMPLE* sample);
VALUE rb_esd_sample_play( VALUE self);
VALUE rb_esd_sample_sync( VALUE self);
VALUE rb_esd_sample_loop( VALUE self);
VALUE rb_esd_sample_stop( VALUE self);
#ifdef HAVE_ESD_SAMPLE_KILL
VALUE rb_esd_sample_kill( VALUE self);
#endif
VALUE rb_esd_sample_pan( VALUE self, VALUE left, VALUE right);

/* EsounD::Stream's methods */
VALUE rb_esd_stream_open( VALUE self, VALUE fmt, VALUE rate, VALUE host);
void rb_esd_stream_free( ESOUND_STREAM* stream);
VALUE rb_esd_stream_play( VALUE self, VALUE data);
VALUE rb_esd_stream_standby( VALUE self);
VALUE rb_esd_stream_resume( VALUE self);
VALUE rb_esd_stream_pan( VALUE self, VALUE left, VALUE right);
VALUE rb_esd_stream_close( VALUE self);


/* klass variables */
VALUE EsounD;
VALUE Stream;
VALUE Sample;

/* exception */
VALUE ConnectError;
VALUE LockError;
VALUE UnlockError;
VALUE CloseError;
VALUE IOError;

/* extern */
extern VALUE rb_eIOError;
extern VALUE rb_cObject;

void
Init_esd()
{
	time_t	a;

	/* class EsounD */
	EsounD = rb_define_class( "ESD", rb_cObject);

	rb_define_const( EsounD, "BUF_SIZE", INT2FIX(ESD_BUF_SIZE));
	rb_define_const( EsounD, "KEY_LEN", INT2FIX(ESD_KEY_LEN));
	rb_define_const( EsounD, "DEFAULT_PORT", INT2FIX(ESD_DEFAULT_PORT));
	rb_define_const( EsounD, "DEFAULT_RATE", INT2FIX(ESD_DEFAULT_RATE));
	rb_define_const( EsounD, "NAME_MAX", INT2FIX(ESD_NAME_MAX));
	rb_define_const( EsounD, "ENDIAN_KEY", INT2FIX(ESD_ENDIAN_KEY));
	rb_define_const( EsounD, "VOLUME_BASE", INT2FIX(ESD_VOLUME_BASE));

	/* bits of stream/sample data */
	rb_define_const( EsounD, "MASK_BITS", INT2FIX(ESD_MASK_BITS));
	rb_define_const( EsounD, "BITS8", INT2FIX(ESD_BITS8));
	rb_define_const( EsounD, "BITS16", INT2FIX(ESD_BITS16));

	/* how many interleaved channels of data */
	rb_define_const( EsounD, "MASK_CHAN", INT2FIX(ESD_MASK_CHAN));
	rb_define_const( EsounD, "MONO", INT2FIX(ESD_MONO));
	rb_define_const( EsounD, "STEREO", INT2FIX(ESD_STEREO));

	/* whether it's a stream or a sample */
	rb_define_const( EsounD, "MASK_MODE", INT2FIX(ESD_STEREO));
	rb_define_const( EsounD, "STREAM", INT2FIX(ESD_STREAM));
	rb_define_const( EsounD, "SAMPLE", INT2FIX(ESD_SAMPLE));
	rb_define_const( EsounD, "ADPCM", INT2FIX(ESD_ADPCM));

	/* the function of the strsample, and common functions */
	rb_define_const( EsounD, "MASK_FUNC", INT2FIX(ESD_MASK_FUNC));
	rb_define_const( EsounD, "PLAY", INT2FIX(ESD_PLAY));
	/* functions for streams only */
	rb_define_const( EsounD, "MONITOR", INT2FIX(ESD_MONITOR));
	rb_define_const( EsounD, "RECORD", INT2FIX(ESD_RECORD));
	/* functions for samples only */
	rb_define_const( EsounD, "STOP", INT2FIX(ESD_STOP));
	rb_define_const( EsounD, "LOOP", INT2FIX(ESD_LOOP));

	rb_define_singleton_method( EsounD, "new", rb_esd_open, 1);
	rb_define_singleton_method( EsounD, "open", rb_esd_open, 1);
	rb_define_method( EsounD, "close", rb_esd_close, 0);
	rb_define_method( EsounD, "lock", rb_esd_lock, 0);
	rb_define_method( EsounD, "unlock", rb_esd_unlock, 0);
	rb_define_method( EsounD, "default_pan", rb_esd_default_pan, 2);
	rb_define_method( EsounD, "cache", rb_esd_cache, 3);
	rb_define_method( EsounD, "file_cache", rb_esd_file_cache, 1);
	rb_define_method( EsounD, "play_file", rb_esd_play_file, 1);

	/* class EsounD::Sample */
	Sample = rb_define_class_under( EsounD, "Sample", rb_cObject);
	rb_define_method( Sample, "free", rb_esd_sample_free, 0);
	rb_define_method( Sample, "play", rb_esd_sample_play, 0);
	rb_define_method( Sample, "sync", rb_esd_sample_sync, 0);
	rb_define_method( Sample, "loop", rb_esd_sample_loop, 0);
	rb_define_method( Sample, "stop", rb_esd_sample_stop, 0);
#ifdef HAVE_ESD_SAMPLE_KILL
	rb_define_method( Sample, "kill", rb_esd_sample_kill, 0);
#endif
	rb_define_method( Sample, "pan", rb_esd_sample_pan, 2);

	/* class EsounD::Stream */
	Stream = rb_define_class_under( EsounD, "Stream", rb_cObject);
	rb_define_singleton_method( Stream, "new", rb_esd_stream_open, 3);
	rb_define_singleton_method( Stream, "open", rb_esd_stream_open, 3);
	rb_define_method( Stream, "play", rb_esd_stream_play, 1);
	rb_define_method( Stream, "standby", rb_esd_stream_standby, 0);
	rb_define_method( Stream, "pause", rb_esd_stream_standby, 0);
	rb_define_method( Stream, "resume", rb_esd_stream_resume, 0);
	rb_define_method( Stream, "pan", rb_esd_stream_pan, 2);
	rb_define_method( Stream, "close", rb_esd_stream_close, 0);

	/* class ConnctError */
	ConnectError = rb_define_class( "ESD_ConnectError", rb_eIOError);
	LockError    = rb_define_class( "ESD_LockError", rb_eIOError);
	UnlockError  = rb_define_class( "ESD_UnlockError", rb_eIOError);
	CloseError   = rb_define_class( "ESD_CloseError", rb_eIOError);
	IOError      = rb_define_class( "ESD_IOError", rb_eIOError);

	/* random initialize */
	time( &a);
	srandom( a);
}

/*
 * EsounD's methods
 */

VALUE rb_esd_open( VALUE self, VALUE host)
{
	VALUE	obj;
	ESOUND	*body;
	char*	esd_host;
	
	body = ALLOC( ESOUND);

	if( host == Qnil){
		esd_host = NULL;
		body->host[0] = '\0';
	} else {
		esd_host = strncpy( body->host, STR2CSTR( host), MAX_HOST_NAME);
	}

	if( (body->sock = esd_open_sound( esd_host)) != -1){
		body->default_right = 255;
		body->default_left  = 255;

		return Data_Wrap_Struct( EsounD, 0, rb_esd_free, body);
	} else {
		if( host == Qnil){
			esd_host = "NIL";
		}
		free( body);
		rb_raise( ConnectError, "EsounD disconnect(%s).", esd_host);
	}

	return Qnil;
}

void rb_esd_free( ESOUND* body )
{
	if( body->sock != -1){
		esd_close( body->sock);
	}

	free( body);
}

VALUE rb_esd_close( VALUE self)
{
	ESOUND *body;

	Data_Get_Struct( self, ESOUND, body);

	if( body->sock != -1){
		esd_close( body->sock);
		body->sock = -1;
	} else {
		rb_raise( CloseError, "object close over try.");
	}

	return self;
}

VALUE rb_esd_lock( VALUE self)
{
	ESOUND *body;
	
	Data_Get_Struct( self, ESOUND, body);

	if( body->sock != -1){
		if( esd_lock( body->sock) != 0){
			/* NG */
			rb_raise( LockError, "lock failed.");
		}
	} else {
		rb_raise( LockError, "lock to closed object.");
	}

	return self;
}

VALUE rb_esd_unlock( VALUE self)
{
	ESOUND *body;
	
	Data_Get_Struct( self, ESOUND, body);

	if( body->sock != -1){
		if( esd_unlock( body->sock) != 0){
			/* NG */
			rb_raise( UnlockError, "lock failed.");
		}
	} else {
		rb_raise( UnlockError, "unlock to closed object.");
	}

	return self;
}

VALUE rb_esd_default_pan( VALUE self, VALUE left, VALUE right)
{
	ESOUND* body;

	Data_Get_Struct( self, ESOUND, body);

	if( body->sock != -1){
		body->default_right = FIX2INT( right);
		body->default_left  = FIX2INT( left);
	} else {
		rb_raise( IOError, "panning set to closed object.");
	}

	return self;
}

VALUE rb_esd_cache( VALUE self, VALUE fmt, VALUE rate, VALUE data)
{
	ESOUND* 		body;
	ESOUND_SAMPLE*	sample;
	int				id;
	int				cfmt;
	int				crate;
	char*			cdata;
	char			cname[ MAX_NAME];
	int				len;
	double			t;
	VALUE			ret = Qnil;

	Data_Get_Struct( self, ESOUND, body);

	/* Extract arguments */
	cfmt  = FIX2INT( fmt);
	if( !(cfmt & ESD_MASK_CHAN)){
		cfmt |= ESD_MONO;
	}
	cfmt |= ESD_SAMPLE;

	if( !(cfmt & ESD_MASK_FUNC)){
		cfmt |= ESD_PLAY;
	}

	crate = FIX2INT( rate);
	cdata = STR2CSTR(data);
	len   = RSTRING(data)->len;
	sprintf( cname, "%010x", random());

	if( body->sock != 1){
		if((id = esd_sample_cache( body->sock, cfmt, crate, len, cname)) != -1){
			write( body->sock, cdata, len);
			esd_confirm_sample_cache( body->sock);

			/* calc palying time */
			t = ((double)len) / (((cfmt & ESD_BITS16)? 2: 1) *
					 ((cfmt& ESD_STEREO)? 2: 1) * ((double)crate));

			/* make EsounD::Sample object */
			sample = ALLOC( ESOUND_SAMPLE);
			sample->parent = body;
			sample->id     = id;
			sample->right  = body->default_right;
			sample->left   = body->default_left;
			sample->time.tv_sec    = (int)t;
			sample->time.tv_usec   = (int)((t - sample->time.tv_sec)*1000000);
			sample->start.tv_sec   = 0;
			sample->start.tv_usec  = 0;
			strncpy( sample->name, cname, MAX_NAME);
			esd_set_default_sample_pan( body->sock, id,
							 body->default_left, body->default_right);

			ret =  Data_Wrap_Struct( Sample, 0, rb_esd_sample_xfree, sample);
		} else {
			/* cache NG */
			rb_raise( IOError, "create sample failed.");
		}
	} else {
		rb_raise( IOError, "create sample date from closed object.");
	}

	return ret;
}

VALUE rb_esd_file_cache( VALUE self, VALUE fname)
{
	int		id;
	char*	cfname;
	double	t;

	ESOUND*				body;
	ESOUND_SAMPLE* 		sample;
	esd_info_t*			all_info;
	esd_sample_info_t*	sample_info;

	
	Data_Get_Struct( self, ESOUND, body);
	cfname = STR2CSTR(fname);

	if( body->sock != 1){
		char	reget_name[ ESD_NAME_MAX] = "Ruby/ESD:";

		esd_file_cache( body->sock, "Ruby/ESD", cfname);
		strncpy( reget_name + 9, cfname, ESD_NAME_MAX - 9);
		id = esd_sample_getid( body->sock, reget_name);

		if(id != -1){
			all_info = esd_get_all_info( body->sock);

			if( all_info != NULL){
				for( sample_info = all_info->sample_list;
				  sample_info; sample_info = sample_info->next){
					if( sample_info->sample_id == id){
						/* calc palying time */
						t = ((double)(sample_info->length)) /
							 (((sample_info->format & ESD_BITS16)? 2: 1) *
							 ((sample_info->format & ESD_STEREO)? 2: 1) *
							 ((double)sample_info->rate));

						/* make EsounD::Sample object */
						sample = ALLOC( ESOUND_SAMPLE);

						sample->parent = body;
						sample->id     = id;
						sample->right  = body->default_right;
						sample->left   = body->default_left;
						sample->time.tv_sec   = (int)t;
						sample->time.tv_usec  =
							(int)((t - sample->time.tv_sec)*1000000);
						sample->start.tv_sec  = 0;
						sample->start.tv_usec = 0;
						strncpy( sample->name, sample_info->name, MAX_NAME);

						esd_set_default_sample_pan( body->sock, id,
								 body->default_left, body->default_right);

						esd_free_all_info (all_info);

						return Data_Wrap_Struct( Sample, 0,
										 rb_esd_sample_xfree, sample);
					}
				}
				rb_raise( IOError, "Really? can`t get Sample info.");
				
			} else {
				rb_raise( ConnectError, "can't get EsounD info.");
			}
		} else {
			/* cache NG */
			rb_raise( IOError, "create sample failed.");
		}
	} else {
		rb_raise( IOError, "create sample date from closed object.");
	}

	return self;
}

VALUE rb_esd_play_file( VALUE self, VALUE fname)
{
	char*	cfname;
	ESOUND*	body;

	cfname = STR2CSTR(fname);
	Data_Get_Struct( self, ESOUND, body);

	esd_play_file( "Ruby/ESD", cfname, body->sock);

	return self;
}

void rb_esd_sample_xfree( ESOUND_SAMPLE* sample)
{
	if( sample->parent->sock != -1){
		esd_sample_free( sample->parent->sock, sample->id);
	}

	free( sample);
}

/*
 * EsounD::Sample's methods
 */
VALUE rb_esd_sample_free( VALUE self)
{
	ESOUND_SAMPLE	*body;

	Data_Get_Struct( self, ESOUND_SAMPLE, body);

	if( body->id != -1){
		if( esd_sample_free( body->parent->sock, body->id) != 0){
			rb_raise( IOError, "free sample failed.");
		} else {
			body->id = -1;
		}
	} else {
		rb_raise( IOError, "free over try.");
	}

	return self;
}

VALUE rb_esd_sample_play( VALUE self)
{
	double			time;
	ESOUND_SAMPLE	*body;

	Data_Get_Struct( self, ESOUND_SAMPLE, body);

	if( body->id != -1){
		if( !esd_sample_play( body->parent->sock, body->id)){
			rb_raise( IOError, "play sample failed.");
		} else {
			gettimeofday( &(body->start), NULL);
		}
	} else {
		rb_raise( IOError, "play to freied sample.");
	}

	time = (double)body->time.tv_sec + ((double)body->time.tv_usec / 1000000);

	return rb_float_new( time);
}

VALUE rb_esd_sample_sync( VALUE self)
{
	int				sec;
	int				usec;
	struct timeval	now;
	ESOUND_SAMPLE	*body;

	Data_Get_Struct( self, ESOUND_SAMPLE, body);

	if( body->start.tv_sec == 0 && body->start.tv_usec == 0){
		rb_raise( IOError, "sync to not plaied sample.");
	} else {
		/* wait = body->time - (now - body->start)
			    = body->time - now + body->start
			    = body->time + body->start - now */

		/* w1 = body->time + body->start */
		sec  = body->time.tv_sec + body->start.tv_sec;
		usec = body->time.tv_usec + body->start.tv_usec;

		/* gettimeofday()とsleep()の間を出来るだけ詰める為
			第一段回目の桁あふれのチェックをここでやる */
		if( usec >= 1000000){
			sec++;
			usec -= 1000000;
		}

		/* wait = w1 - now */
		gettimeofday( &now, NULL);

		sec  -= now.tv_sec;
		usec -= now.tv_usec;

		if( usec < 0){
			sec--;
			usec += 1000000;
		}

		/* SLEEP! */
		if( sec >= 0){
			if( sleep( sec) == 0){
				/* NOT interrupt */
				usleep( usec);
			}
		}
	}

	return self;
}

VALUE rb_esd_sample_loop( VALUE self)
{
	ESOUND_SAMPLE	*body;

	Data_Get_Struct( self, ESOUND_SAMPLE, body);

	if( body->id != -1){
		if( !esd_sample_loop( body->parent->sock, body->id)){
			rb_raise( IOError, "loop sample failed.");
		}
	} else {
		rb_raise( IOError, "loop to freied sample.");
	}

	return self;
}

VALUE rb_esd_sample_stop( VALUE self)
{
	ESOUND_SAMPLE	*body;

	Data_Get_Struct( self, ESOUND_SAMPLE, body);

	if( body->id != -1){
		if( !esd_sample_stop( body->parent->sock, body->id)){
			rb_raise( IOError, "stop sample failed.");
		}
	} else {
		rb_raise( IOError, "stop to freied sample.");
	}

	return self;
}


#ifdef HAVE_ESD_SAMPLE_KILL
VALUE rb_esd_sample_kill( VALUE self)
{
	ESOUND_SAMPLE	*body;

	Data_Get_Struct( self, ESOUND_SAMPLE, body);

	if( body->id != -1){
		if( !esd_sample_kill( body->parent->sock, body->id)){
			rb_raise( IOError, "kill sample failed.");
		} else {
			body->start.tv_sec   = 0;
			body->start.tv_usec  = 0;
		}
	} else {
		rb_raise( IOError, "kill to freied sample.");
	}

	return self;
}
#endif

VALUE rb_esd_sample_pan( VALUE self, VALUE left, VALUE right)
{
	int				cleft, cright;
	ESOUND_SAMPLE	*body;

	Data_Get_Struct( self, ESOUND_SAMPLE, body);

	cleft  = FIX2INT( left);
	cright = FIX2INT( right);

	printf( "%d %d\n", cleft, cright);

	if( body->id != -1){
		if( !esd_set_default_sample_pan(
			 	body->parent->sock, body->id, cleft, cright)){
			rb_raise( IOError, "set sample panning failed.");
		}
	} else {
		rb_raise( IOError, "set panning to freied sample.");
	}

	return self;
}

/*
 * EsounD::Stream's methods
 */
VALUE rb_esd_stream_open( VALUE self, VALUE fmt, VALUE rate, VALUE host)
{
	VALUE				obj;
	ESOUND_STREAM*		body;
	int					cfmt, crate;
	char*				cname;
	char*				chost;

	esd_info_t*			all_info;
	esd_player_info_t*	player_info;

	body  = ALLOC( ESOUND_STREAM);

	cfmt  = FIX2INT( fmt);
	crate = FIX2INT( rate);

	if( host == Qnil){
		chost = NULL;
		body->host[0] = '\0';
	} else {
		chost = strncpy( body->host, STR2CSTR( host), MAX_HOST_NAME);
	}

	sprintf( body->name, "%010x", random());

	if((body->sock = 
			esd_play_stream_fallback( cfmt, crate, chost, body->name)) >=0){

		int	sock;

		if( ( sock = esd_open_sound( chost)) < 0){
			free( body);
			rb_raise( ConnectError, "can't get EsounD info.");
		}

		all_info = esd_get_all_info( sock);
		esd_close( sock);

		if( all_info != NULL){
			for( player_info = all_info->player_list;
			  player_info; player_info = player_info->next){
				if(!strcmp(player_info->name, body->name)){
					body->id = player_info->source_id;
					break;
				}
			}
			esd_free_all_info (all_info);
			obj = Data_Wrap_Struct( Stream, 0, rb_esd_free, body);
		} else {
			free( body);
			rb_raise( ConnectError, "can't get EsounD info.");
		}

	} else {
		if( host == Qnil){
			chost = "NIL";
		}
		free( body);
		rb_raise( ConnectError, "EsounD disconnect(%s).", chost);
	}

	return obj;
}

void rb_esd_stream( ESOUND_STREAM* stream)
{
	if( stream->sock != -1){
		esd_close( stream->sock);
	}

	free( stream);
}

VALUE rb_esd_stream_play( VALUE self, VALUE data)
{
	ESOUND*	body;
	char*	cdata;
	long	length;

	Data_Get_Struct( self, ESOUND, body);

	cdata  = RSTRING( data)->ptr;
	length = RSTRING( data)->len;

	if( body->sock != -1){
		if( write( body->sock, cdata, length) != length){
			rb_raise( IOError, "can't finish write audio data.");
		}
	} else {
		rb_raise( IOError, "play to closed stream object.");
	}

	return self;
}

VALUE rb_esd_stream_standby( VALUE self)
{
	ESOUND*	body;

	Data_Get_Struct( self, ESOUND, body);

	if( body->sock != -1){
		esd_standby( body->sock);
	} else {
		rb_raise( CloseError, "stream standby to closed object.");
	}
	
	return self;
}

VALUE rb_esd_stream_resume( VALUE self)
{
	ESOUND*	body;

	Data_Get_Struct( self, ESOUND, body);

	if( body->sock != -1){
		esd_resume( body->sock);
	} else {
		rb_raise( CloseError, "stream resume to closed object.");
	}
	
	return self;
}

VALUE rb_esd_stream_pan( VALUE self, VALUE left, VALUE right)
{
	int				cleft, cright;
	ESOUND_STREAM*	body;

	Data_Get_Struct( self, ESOUND_STREAM, body);

	cleft  = FIX2INT( left);
	cright = FIX2INT( right);
	
	if( body->sock != -1){
		if( !esd_set_stream_pan( body->sock, body->id, cleft, cright)){
			rb_raise( IOError, "set strem panning failed.");
		}
	} else {
		rb_raise( IOError, "set panning to closed stream object.");
	}

	return self;
}

VALUE rb_esd_stream_close( VALUE self)
{
	ESOUND*	body;

	Data_Get_Struct( self, ESOUND, body);

	if( body->sock != -1){
		esd_close( body->sock);
		body->sock = -1;
	} else {
		rb_raise( CloseError, "object close over try.");
	}
	
	return self;
}
