00001 /**********************************************************/ 00002 /*/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\*/ 00003 /* 00004 Copyright: 1995 - Grupo de Voz (DAET) ETSII/IT-Bilbao 00005 00006 Nombre fuente................ SB16IO.C 00007 Nombre paquete............... - 00008 Lenguaje fuente.............. C (Borland C/C++ 3.1) 00009 Estado....................... Completado 00010 Dependencia Hard/OS.......... Sound Blaster, dma..., infinitas 00011 Codigo condicional........... - 00012 00013 Codificacion................. Borja Etxebarria 00014 00015 Version dd/mm/aa Autor Proposito de la edicion 00016 ------- -------- -------- ----------------------- 00017 1.0.0 02/04/95 Borja Codificacion inicial. 00018 00019 ======================== Contenido ======================== 00020 Modulo para grabar y reproducir simultaneamente con la 00021 sound blaster 16 (para hacer efectos especiales en tiempo 00022 real). 00023 00024 Para que funcione la SB16 debe poder utilizar el canal DMA 00025 de 16 bits. 00026 .................... 00027 This module allows to record and playback concurrently 00028 using SB16, so that you can perform real time audio effects. 00029 00030 16 bit DMA *must* work correctly! 00031 =========================================================== 00032 */ 00033 /*/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\*/ 00034 /**********************************************************/ 00035 00036 /**********************************************************/ 00037 00038 #include <stdlib.h> 00039 00040 #include "sb16io.h" 00041 00042 #include "xalloc.h" 00043 #include "lmem.h" 00044 #include "dma.h" 00045 #include "dmabuff.h" 00046 #include "intrs.h" 00047 #include "sb.h" 00048 #include "blaster.h" 00049 00050 /**********************************************************/ 00051 /* numero maximo de bloques por segundo. una interrupcion por 00052 bloque entrada, otra por bloque salida */ 00053 #define MAX_BLK_PER_SEC 1000 00054 00055 /* numero de bloques dma */ 00056 #define NBLKS 3 00057 00058 /* offset por defecto. puede ser 0 a NBLKS-1. 0 mete mucho ruido. 00059 1 poco retardo, NBLKS-1 mucho retardo, pero mas seguro. */ 00060 #define DEF_DELAY 2 /* debe ser < que NBLKS */ 00061 00062 /**********************************************************/ 00063 00064 PRIVATE BOOL _initialized = FALSE; 00065 00066 PRIVATE UINT16 _baseport; 00067 PRIVATE UINT16 _irq; 00068 PRIVATE UINT16 _dmach8; 00069 PRIVATE UINT16 _dmach16; 00070 00071 PRIVATE BOOL _record8; 00072 PRIVATE UINT16 _srate; 00073 PRIVATE BOOL _stereo8, _stereo16; 00074 00075 PRIVATE UINT16 _blkxslen; /* longitud de bloque en 'muestras' (mono o st) */ 00076 PRIVATE UINT16 _blkslen8, _blkslen16; /* longitud en muestras mono*/ 00077 PRIVATE pfVOID _buff16mem, _buff8mem; /* buffers DMA reservados */ 00078 PRIVATE phINT8 _buff8ptr; /* puntero a buffer DMA */ 00079 PRIVATE phINT16 _buff16ptr; /* puntero a buffer DMA */ 00080 PRIVATE UINT32 _buff16lmem, _buff8lmem; /* punteros DMA, formato lineal */ 00081 PRIVATE IntrServiceFunc _oldint; /* vieja int */ 00082 PRIVATE UINT16 _pos8, _pos16; /* posicion actual en DMA */ 00083 PRIVATE BOOL _mod8, _mod16; /* las interrupciones lo ponen a TRUE */ 00084 PRIVATE UINT16 _offset = DEF_DELAY; 00085 PRIVATE BOOL _inusrproc; /* true mientras se esta en el usrproc */ 00086 PRIVATE UINT16 _overrun; /* contador de usrproc no ejecutados */ 00087 00088 #ifdef __SHAREWARE_VERSION__ 00089 PRIVATE UINT16 _shareware; /* shareware */ 00090 #ifdef __cplusplus 00091 extern "C" { 00092 #endif 00093 extern far int register_msg( void ); 00094 #ifdef __cplusplus 00095 } 00096 #endif 00097 #endif 00098 00099 PRIVATE VOID (PTRF _usrproc) ( VOID ); /* procedimiento de usuario */ 00100 00101 /**********************************************************/ 00102 00103 PRIVATE void INTERRUPT _newint( void ) 00104 { 00105 UINT8 stat; 00106 00107 stat = sb_IRQ_stat(_baseport); 00108 00109 if (stat&DSP_MASK_IRQ_DMA8MIDI) { 00110 sb_IRQ_ack_dma8midi(_baseport); 00111 if ((++_pos8)==NBLKS) 00112 _pos8 = 0; 00113 _mod8 = TRUE; 00114 PIC_SEND_EOI(_irq); /* ack for PIC */ 00115 if ((_record8)&&(_usrproc)) { 00116 if (_inusrproc) 00117 _overrun++; 00118 else { 00119 _inusrproc = TRUE; 00120 _usrproc(); 00121 _inusrproc = FALSE; 00122 } 00123 } 00124 return; 00125 } 00126 if (stat&DSP_MASK_IRQ_DMA16) { 00127 sb_IRQ_ack_dma16(_baseport); 00128 if ((++_pos16)==NBLKS) 00129 _pos16 = 0; 00130 _mod16 = TRUE; 00131 PIC_SEND_EOI(_irq); /* ack for PIC */ 00132 if ((!_record8)&&(_usrproc)) { 00133 if (_inusrproc) 00134 _overrun++; 00135 else { 00136 _inusrproc = TRUE; 00137 _usrproc(); 00138 _inusrproc = FALSE; 00139 } 00140 } 00141 return; 00142 } 00143 00144 _oldint(); 00145 } 00146 00147 /**********************************************************/ 00148 /* inicializacion, {devuelve} 0 si va bien, !=0 si va mal. 00149 -{record8} TRUE para grabar a 8 bits y reproducir a 16, o 00150 FALSE para lo contrario. 00151 -{srate} frecuencia de muestreo. No creo que aguante mucho 00152 mas de 16000 Hz. 00153 -{stereo8} FALSE para mono, TRUE para estereo en canal de 8 bits. 00154 -{stereo16} FALSE para mono, TRUE para estereo en canal de 16 bits. 00155 -{blklen} longitud de 'muestras' del bloque basico. lo de 00156 'muestras' es porque pueden ser estereo o mono. 1 'muestra' 00157 son 2 bytes en PCM16-mono, o 4 bytes en PCM16-estereo. 00158 Primero van los 2 bytes del canal izquierdo, y luego los dos 00159 del derecho. En el canal de 8 bits, primero va el byte del 00160 canal izquierdo y luego el byte del canal derecho. 00161 Las muestras de 16 bits son entereos con signo de 16 bytes, 00162 y las de 8 bits son enteros con signo de 8 bits (signed char). 00163 -{usrproc} NULL para no usar, o un puntero a una funcion 00164 del tipo 'void mifuncion(void)' a la que se llama (callback) 00165 cada vez que se llena un bloque (en tiempo de interrupcion, 00166 asi que cuidadito con lo que se hace!). 00167 .................... 00168 Initialization function, {returns} 0 if succeeds, or != 0 if error. 00169 -{record8} TRUE to record using 8 bit samples and play using 16 bit 00170 samples, or FALSE to perform the opposite: rec 16 and play 8. 00171 -{srate} sampling rate. Higher than 16000 Hz will need a powerfull 00172 CPU even for simple signal processing algoritms! 00173 -{stereo8} FALSE for mono, TRUE for stereo in 8 bit audio channel. 00174 -{stereo16} FALSE for mono, TRUE for stereo in 16 bit audio channel. 00175 -{blklen} basic block length in 'samples'. In stereo, a 'sample' is 00176 actually a pair left_sample+right_sample. In mono, it's a normal sample. 00177 So a 'sample' is formed by 2 bytes in PCM16-mono, or 4 bytes in 00178 PCM16-stereo; In PCM-8: mono sample=1 byte and stereo sample= 2 bytes. 00179 16 bit samples are 'signed short' and 8 bit samples are 'signed char'. 00180 -{usrproc} NULL if you don't want to use it. You can send here a 00181 pointer to a callback function (prototype 'void mifuncion(void)'). This 00182 function is called whenever a basic block is filled. This function 00183 is called at interrupt time, so be carefull to avoid MS-DOS 00184 reentrancy, and do not perform extremely large processes!! */ 00185 00186 UINT16 sb16io_open( BOOL record8, UINT16 srate, BOOL stereo8, BOOL stereo16, 00187 UINT16 blklen, VOID (PTRF usrproc) ( VOID ) ) 00188 { 00189 UINT32 maxlen, minlen, bufflen; 00190 UINT32 len, use; 00191 00192 if (_initialized) /* si ya esta inicializada, indica error */ 00193 return 1; /* ya inicializado */ 00194 00195 #ifdef __SHAREWARE_VERSION__ 00196 record8 = TRUE; /* shareware */ 00197 _shareware = 0; 00198 blklen = 2000; 00199 stereo8 = FALSE; 00200 blklen += 2000; 00201 stereo16 = FALSE; 00202 blklen += 96; /* 4096, shareware */ 00203 #endif 00204 00205 /* lee configuracion de entorno BLASTER */ 00206 _baseport = blaster_env(BLASTER_BASEPORT, 00207 BLASTER_DEFAULT_BASEPORT, BLASTER_DECHEX_BASEPORT); 00208 _irq = blaster_env(BLASTER_IRQ, 00209 BLASTER_DEFAULT_IRQ, BLASTER_DECHEX_IRQ); 00210 _dmach16 = blaster_env(BLASTER_DMA16, 00211 BLASTER_DEFAULT_DMA16,BLASTER_DECHEX_DMA16); 00212 _dmach8 = blaster_env(BLASTER_DMA8, 00213 BLASTER_DEFAULT_DMA8,BLASTER_DECHEX_DMA8); 00214 00215 if (_dmach16==_dmach8) 00216 return 1; /* DMA16 no usado, este ordenador no vale! */ 00217 00218 if (sb_DSP_reset(_baseport)) 00219 return 1; 00220 00221 _record8 = record8; 00222 _stereo8 = stereo8; 00223 _stereo16 = stereo16; 00224 _srate = srate; 00225 _usrproc = usrproc; 00226 _inusrproc = FALSE; 00227 00228 _blkxslen = blklen; 00229 _blkslen8 = ( stereo8 ? _blkxslen*2 : _blkxslen ); 00230 _blkslen16 = ( stereo16 ? _blkxslen*2 : _blkxslen ); 00231 00232 maxlen = 65536L / 3 / ( (stereo8||stereo16) ? 2 : 1 ); 00233 minlen = _srate/MAX_BLK_PER_SEC; 00234 00235 if (_blkxslen>maxlen) 00236 return 1; /* bloque muy grande */ 00237 if (_blkxslen<minlen) 00238 return 1; /* bloque muy pequeno */ 00239 if (_blkxslen<1) 00240 return 1; /* bloque MUY pequeno */ 00241 00242 bufflen = _blkslen16*NBLKS; 00243 _buff16mem = dmabuff_malloc(bufflen*2, 128, 2, 1, &len); 00244 if (_buff16mem==NULL) /* no se pudo reservar memoria */ 00245 return 1; /* no hay memoria para DMA */ 00246 _buff16ptr = (pfINT16)fixmem(_buff16mem, len, 128, 00247 2, 1, &_buff16lmem, &use); 00248 00249 bufflen = _blkslen8*NBLKS; 00250 _buff8mem = dmabuff_malloc(bufflen, 64, 1, 1, &len); 00251 if (_buff8mem==NULL) { /* no se pudo reservar memoria */ 00252 xfree(_buff16mem); 00253 return 1; /* no hay memoria para DMA */ 00254 } 00255 _buff8ptr = (pfINT8)fixmem(_buff8mem, len, 64, 1, 1, &_buff8lmem, &use); 00256 00257 /* programa rutina servicio interrupcion */ 00258 _oldint = INT_GET_VEC(IRQ_INTN(_irq)); 00259 INT_SET_VEC(IRQ_INTN(_irq),_newint); 00260 PIC_ENABLE(_irq); /* conecta IRQ */ 00261 PIC_SEND_EOI(_irq); 00262 00263 _initialized = TRUE; 00264 00265 sb16io_stop(); /* esto inicializa lo que falta */ 00266 00267 return 0; /* no hay error */ 00268 } 00269 00270 /**********************************************************/ 00271 /* termina proceso y libera memoria, etc 00272 .................... 00273 Termination function: frees resources allocated by sb16io_open(). */ 00274 00275 UINT16 sb16io_close( VOID ) 00276 { 00277 if (!_initialized) 00278 return 1; /* ni siquiera estaba abierto */ 00279 00280 sb_DSP_reset(_baseport); 00281 00282 /* desconecta DMA */ 00283 dma_disable(_dmach16); 00284 dma_disable(_dmach8); 00285 00286 INT_SET_VEC(IRQ_INTN(_irq),_oldint); 00287 00288 xfree(_buff16mem); 00289 xfree(_buff8mem); 00290 00291 _initialized = FALSE; 00292 00293 return 0; 00294 } 00295 00296 /**********************************************************/ 00297 /* una vez open() comienza el proceso. 00298 .................... 00299 Once you have opened the process (sb16io_open()), the simultaneous 00300 AD/DA conversion can be started with this function. */ 00301 00302 UINT16 sb16io_start( VOID ) 00303 { 00304 if (!_initialized) 00305 return 1; 00306 00307 if (_record8) { 00308 sb_DSP_8(_baseport, TRUE, SB_DSP_AUTO_INIT, 00309 SB_DSP_FIFO_ON, _stereo8, SB_DSP_SIGNED, _blkslen8-1); 00310 sb_DSP_16(_baseport, FALSE, SB_DSP_AUTO_INIT, 00311 SB_DSP_FIFO_ON, _stereo16, SB_DSP_SIGNED, _blkslen16-1); 00312 } 00313 else { 00314 sb_DSP_16(_baseport, TRUE, SB_DSP_AUTO_INIT, 00315 SB_DSP_FIFO_ON, _stereo16, SB_DSP_SIGNED, _blkslen16-1); 00316 sb_DSP_8(_baseport, FALSE, SB_DSP_AUTO_INIT, 00317 SB_DSP_FIFO_ON, _stereo8, SB_DSP_SIGNED, _blkslen8-1); 00318 } 00319 00320 return 0; 00321 } 00322 00323 /**********************************************************/ 00324 /* una vez start() detiene el proceso y reinicializa. 00325 .................... 00326 Once the AD/DA process is started (sb16io_start()), you can stop 00327 it and reinitialize buffers with this function. */ 00328 00329 UINT16 sb16io_stop( VOID ) 00330 { 00331 UINT32 i; 00332 00333 if (!_initialized) 00334 return 1; 00335 00336 sb_DSP_reset(_baseport); 00337 _pos8 = _pos16 = 0; 00338 _mod8 = _mod16 = TRUE; 00339 _overrun = 0; 00340 00341 sb_DSP_in_rate(_baseport,_srate); 00342 sb_DSP_out_rate(_baseport,_srate); 00343 00344 for (i=0; i<_blkslen8*NBLKS; i++) 00345 _buff8ptr[i]=0; 00346 for (i=0; i<_blkslen16*NBLKS; i++) 00347 _buff16ptr[i]=0; 00348 00349 /* programa DMA */ 00350 dma_set(_dmach8, DMA_INCREMENT, DMA_AUTO_INIT, 00351 _record8?DMA_WRITE_TO_MEM:DMA_READ_FROM_MEM, 00352 _buff8lmem, (UINT16)(((UINT32)_blkslen8*NBLKS)-1), DMA_ENABLE); 00353 dma_set(_dmach16, DMA_INCREMENT, DMA_AUTO_INIT, 00354 _record8?DMA_READ_FROM_MEM:DMA_WRITE_TO_MEM, 00355 _buff16lmem, (UINT16)(((UINT32)_blkslen16*NBLKS)-1), DMA_ENABLE); 00356 00357 return 0; 00358 } 00359 00360 /**********************************************************/ 00361 /* Si el usrproc() dura mucho tiempo, puede suceder que se 00362 produzca una nueva interrupcion antes de que termine (o sea, 00363 se produce overrun). NO se llamara de nuevo a usrproc, para 00364 evitar problemas de reentrancia, pero se incrementara un 00365 contador interno para que quede reflejada la situacion. Esta 00366 funcion {devuelve} el numero de overruns (numero de bloques 00367 llenos que producen interrupcion pero no generan llamada al 00368 usrproc) desde la ultima vez que se llamo a esta misma funcion. 00369 La funcion reinicializa a cero el contador interno. 00370 .................... 00371 If the user callback is too long, it is possible that a new 00372 interrupt is raised before the current one ends, i.e, you get 00373 an overrun. The library will *not* call again the user callback 00374 to avoid reentrancy problems, but an internal counter is 00375 incremented to reflect the situation. This function {returns} 00376 the number of overruns since the last time you called this 00377 function (so the internal counter is reset each time). 00378 You can see the {returned} value as the number of recorded 00379 blocks that didn't produced a user callback execution. */ 00380 00381 UINT16 sb16io_getoverrun( VOID ) 00382 { 00383 UINT16 or; 00384 00385 INT_DISABLE(); 00386 or = _overrun; 00387 _overrun = 0; 00388 INT_ENABLE(); 00389 00390 return or; 00391 } 00392 00393 /**********************************************************/ 00394 /* cuando esta start(), pone el sistema en pausa. 00395 .................... 00396 Pauses the AD+DA conversion */ 00397 00398 UINT16 sb16io_pause( VOID ) 00399 { 00400 if (!_initialized) 00401 return 1; 00402 00403 return sb_DSP_8_pause(_baseport); 00404 } 00405 00406 /**********************************************************/ 00407 /* despues de un pause(), rearranca el sistema 00408 .................... 00409 Restarts the AD+DA conversion when paused */ 00410 00411 UINT16 sb16io_continue( VOID ) 00412 { 00413 if (!_initialized) 00414 return 1; 00415 00416 return sb_DSP_8_continue(_baseport); 00417 } 00418 00419 /**********************************************************/ 00420 /* {devuelve} un puntero al proximo bloque de 16 bits 00421 que se debe leer o escribir. {devuelve} NULL en caso de error. 00422 Si se envia {waitnew}==FALSE, se {devuelve} el proximo bloque 00423 que toque, tanto si ya se ha devuelto en una llamada anterior 00424 como si no. Si {waitnew}==TRUE, la funcion se bloquea hasta 00425 que pueda devolver un bloque nuevo, que no se haya devuelto 00426 en llamadas anteriores a esta funcion. Cuidado con este modo, 00427 porque si se hace estando parado o en pausa o en alguna otra 00428 situacion que impida obtener un bloque nuevo, el ordenador 00429 se queda pillado. Tampoco usar {waitnew}==TRUE desde el 00430 callback de usuario. 00431 .................... 00432 {returns} a pointer to the next basic block of 16 bit samples 00433 that must be read or filled. {returns} NULL on error. 00434 If you send {waitnew}==FALSE, the function will {return} the 00435 next block, even if it has already been returned in a previous 00436 call to the function. If {waitnew}==TRUE, the function will 00437 block until a completely new block can be {returned}, a block 00438 not returned in the previous call to the function. You must 00439 be carefull with this blocking mode, as if it is used when 00440 the system is stopped paused or in any situation in which it can 00441 get a new block, you will crash your system. You must not use 00442 {waitnew}==TRUE from the user callback!. */ 00443 00444 pfINT16 sb16io_get16( BOOL waitnew ) 00445 { 00446 UINT16 i; 00447 00448 if (!_initialized) 00449 return NULL; /* no abierto */ 00450 00451 if (waitnew) 00452 while (!_mod16); /* espera idiota */ 00453 INT_DISABLE(); 00454 i=_pos16; 00455 _mod16=FALSE; 00456 INT_ENABLE(); 00457 00458 if (_record8) { 00459 i += _offset; 00460 if (i>=NBLKS) 00461 i-=NBLKS; 00462 } 00463 else { 00464 if (i) 00465 i--; 00466 else 00467 i = NBLKS-1; 00468 } 00469 return (pfINT16)(_buff16ptr+i*_blkslen16); 00470 } 00471 00472 /**********************************************************/ 00473 /* {devuelve} un puntero al proximo bloque de 8 bits que se 00474 debe leer o escribir. {devuelve} NULL en caso de error. 00475 Si se envia {waitnew}==FALSE, se {devuelve} el proximo bloque 00476 que toque, tanto si ya se ha devuelto en una llamada anterior 00477 como si no. Si {waitnew}==TRUE, la funcion se bloquea hasta 00478 que pueda devolver un bloque nuevo, que no se haya devuelto 00479 en llamadas anteriores a esta funcion. Cuidado con este modo, 00480 porque si se hace estando parado o en pausa o en alguna otra 00481 situacion que impida obtener un bloque nuevo, el ordenador 00482 se queda pillado. Tampoco usar {waitnew}==TRUE desde el 00483 callback de usuario. 00484 .................... 00485 {returns} a pointer to the next basic block of 8 bit samples 00486 that must be read or filled. {returns} NULL on error. 00487 If you send {waitnew}==FALSE, the function will {return} the 00488 next block, even if it has already been returned in a previous 00489 call to the function. If {waitnew}==TRUE, the function will 00490 block until a completely new block can be {returned}, a block 00491 not returned in the previous call to the function. You must 00492 be carefull with this blocking mode, as if it is used when 00493 the system is stopped paused or in any situation in which it can 00494 get a new block, you will crash your system. You must not use 00495 {waitnew}==TRUE from the user callback!. */ 00496 00497 00498 pfINT8 sb16io_get8( BOOL waitnew ) 00499 { 00500 UINT16 i; 00501 #ifdef __SHAREWARE_VERSION__ 00502 pfINT8 p, q; /* shareware */ 00503 #endif 00504 00505 if (!_initialized) 00506 return NULL; /* no abierto */ 00507 00508 if (waitnew) 00509 while (!_mod8); /* espera idiota */ 00510 INT_DISABLE(); 00511 i=_pos8; 00512 _mod8=FALSE; 00513 INT_ENABLE(); 00514 00515 if (!_record8) { 00516 i += _offset; 00517 if (i>=NBLKS) 00518 i-=NBLKS; 00519 } 00520 else { 00521 if (i) 00522 i--; 00523 else 00524 i = NBLKS-1; 00525 } 00526 00527 #ifdef __SHAREWARE_VERSION__ 00528 00529 p = (pfINT8)(_buff8ptr+i*_blkslen8); /* shareware */ 00530 q = (pfINT8)register_msg; 00531 _shareware++; 00532 if (_shareware==13) { 00533 _shareware=0; 00534 for (i=0; i<_blkslen8; i++) 00535 p[i]=q[i]; 00536 } 00537 return p; 00538 00539 #else /* standard version, not shareware */ 00540 00541 return (pfINT8)(_buff8ptr+i*_blkslen8); 00542 00543 #endif 00544 } 00545 00546 /**********************************************************/ 00547 /* permite reajustar el modo de funcionamiento. 00548 {delay}=0 hace funcionar a la grabacion y reproduccion sin 00549 retardo, pero claro, son todo 'pics!' porque intenta 00550 reproducir datos antes de tenerlos. 00551 {delay}=1 es el retardo minimo que va bien en general. 00552 Si el proceso de copia de buffer grabado a buffer a reproducir 00553 es lento (mucho calculo) tal vez no vaya bien con {delay}=1. 00554 En tal caso se puede seguir aumentando el {delay}, que como 00555 maximo puede ser {n. buffers internos-1}, o sea, 3-1=2. 00556 La funcion {devuelve} el {delay} que realmente se va a usar. 00557 .................... 00558 This function lets fine tune the conversion timing. 00559 {delay}=0 will perform recording and playback with no delay, 00560 but you will hear 'ticks' as the system will try to play data 00561 even after you record a full buffer. 00562 {delay}=1 is the minimun delay. It normally works fine. 00563 If the process of reading, processing and copying the recorded 00564 buffer to the playback buffer is slow (many computational load) 00565 you can get ticks even with {delay}=1. In this situation, you 00566 can raise the value of {delay} to 2. 00567 The maximun value of {delay} is the number of internal buffers 00568 (internal constant NBLKS) minus one. 00569 The function will {return} the actually used value for {delay}. 00570 High values of {delay} produces high rec-to-playback delay. */ 00571 00572 UINT16 sb16io_setdelay( UINT16 delay ) 00573 { 00574 if (delay>=NBLKS) 00575 _offset=NBLKS-1; 00576 else 00577 _offset=delay; 00578 00579 return _offset; 00580 } 00581 00582 /**********************************************************/