/* * 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) * */ /* 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 #include #include #include #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; }