jueves, 26 de marzo de 2009

Compresión/Limitación

Introducción

Como el nombre indica, la compresión reduce el rango dinámico de una señal. Se utiliza extensivamente en la grabación de audio, trabajo de producción, reducción de ruido, y en aplicaciones de actuaciones en vivo, pero debe ser utilizada con cuidado. Se suele decir que los compresores hacen más fuertes los sonidos silenciosos, y acallan los sonidos más altos,
pero esto es sólo la mitad de verdad.

¿Cómo trabaja?

Un compresor es básicamente un dispositivo de ganancia variable, donde la cantidad de ganancia utilizada depende del nivel de la entrada. En este caso, la ganancia se reducirá cuando el nivel de la señal es alta lo que hará más fuerte pasajes suaves, reduciendo el rango dinámico. El
esquema básico se muestra en la Figura 1.


Figura 1: diagrama de flujo de un compresor. También es posible hacer la detección de nivel después de que la ganancia se aplique (un compresor feedback, en lugar de feedforward).

La relación entrada / salida de un compresor a menudo es descrita por un simple gráfico, como en la Figura 2. El eje horizontal corresponde a la señal de entrada, y el eje vertical es el nivel de salida (ambos medidos en decibelios). Una línea a 45 grados corresponde a una ganancia de uno - cualquier nivel de entrada se asigna a exactamente el mismo nivel de salida. El compresor cambia la pendiente (hace que sea más horizontal) de la línea por encima de algún valor llamado el umbral <b>Threshold

miércoles, 4 de marzo de 2009

Medidas de amplitud de señales de audio

Capítulo I

La propiedad más importante de una señal de audio digital es la amplitud. Desafortunadamente, la amplitud de la señal no tiene una definición canónica. Hablando estrictamente, todas las muestras en un señal de audio digital son en sí mismas amplitud, y también hablamos de la amplitud a de una sinusoide en su conjunto. Es útil hacer medidas de amplitud para las señales de audio digital en general. La amplitud es mejor pensarla como aplicándola una ventana (window), como una serie fija de muestras de la señal. Por ejemplo, la ventana comenzando en la muestra M de longitud N de una señal de audio x[n] consiste en los samples,


Las dos medidas usadas con más frecuencia son el pico de amplitud, que es simplemente la muestra mayor (en valor absoluto) sobre la ventana:

Y la amplitud media raíz cuadrada (root mean square) RMS:


donde es la potencia media definida como:


(En esta última fórmula, los signos de los valores absolutos no son necesarios de momento ya que estamos trabajando con valores reales, pero serán importantes cuando consideremos señales con valores complejos)


Amplitudes de señales RMS y pico comparadas. Par una sinusoide, el pico de amplitud es mayor que el RMS por un factor de .

Ni la amplitud de pico ni la RMS de una señal pueden ser negativas, y pueden ser cero sólo si la señal es cero para todos los n en la ventana.

La amplitud RMS de una señal puede ser igual al pico de amplitud, pero nunca excederlo; y puede ser menor que veces el pico de amplitud, pero nunca menor que esto.

Bajo unas condiciones razonables – si la ventana contiene varios periodos y la frecuencia angular es menor de un radian por muestra- el pico de amplitud de la sinusoide de Sinusoides, amplitud y frecuencia 1 es aproximadamente a y su amplitud RMS es .

Basado en "The Theory and Technique of Electronic Music" Miller Puckette



martes, 3 de marzo de 2009

Sobre el software DSP (parte III)

Capítulo IV

Velocidad de ejecución: Hardware


La potencia de las computadoras está aumentando tan rápidamente, que cualquier libro sobre el tema estará obsoleto antes de que sea publicado. El IBM PC original fue presentado en 1981, con base en el microprocesador 8088 con un reloj de 4,77 MHz y un bus de datos de 8 bits. Este fue seguido por una nueva generación de computadoras personales que se están introduciendo cada 3-4 años 8088→ 80286→ 80386→ 80486→ 80586 (Pentium). Cada uno de estos nuevos sistemas de computación impulsó la velocidad por un factor de cinco más que la tecnología anterior. En 1996, la velocidad de reloj había aumentado a 200 MHz, y el bus de datos a 32 bits. Con otras mejoras, esto se tradujo en un aumento de la potencia de las computadoras de casi mil en sólo 15 años. Podemos esperar otro factor de mil en los próximos 15 años.

La única forma de obtener información al día en la rápida evolución de este campo es directamente de los fabricantes: anuncios, folletos, listas de precios, etc. Olvídese de los datos de rendimiento de libros, revistas y busque en su diario. Esperamos que la velocidad de cálculo en bruto será más del doble cada dos años. Aprender sobre el estado actual de la potencia de las computadoras no es suficiente; necesitas comprender y seguir la forma en que está evolucionando.

Teniendo esto en cuenta, podemos saltar a una visión general de cómo la velocidad de ejecución está limitada por el hardware. Dado que las computadoras se componen de muchos subsistemas, el tiempo necesario para ejecutar una tarea en particular dependerá de dos factores principales: (1) la velocidad de los distintos subsistemas, y (2) el tiempo que se toma para la transferencia de datos entre estos bloques. La Figura 4-5 muestra un esquema simplificado de los más importantes componentes de la limitación de velocidad en un típico ordenador personal. La Unidad Central de Procesamiento (CPU) es el corazón del sistema. Como se describe anteriormente, se compone de una docena de registros, cada uno con capacidad para 32 bits (en la actual generación de computadoras personales). También se incluye en la CPU la electrónica digital necesaria para las operaciones rudimentarias, como el movimiento en torno a los bits y aritmética de punto fijo.

La mayoría de las matemáticas implicadas se manejan transfiriendo los datos a un circuito de hardware especial denominado coprocesador matemático (también llamado una unidad lógica aritmética, o ALU). El coprocesador matemático puede estar contenido en el mismo chip de la CPU, o puede ser otro dispositivo electrónico. Por ejemplo, la adición de dos números de punto flotante requeriría que la CPU transfiriera 8 bytes (4 por cada número) al coprocesador matemático, y de varios bytes que describieran qué hacer con los datos. Después de un corto tiempo de cálculo, el coprocesador matemático pasaría cuatro bytes de nuevo a la CPU, que contiene el número de punto flotante que es la suma. La mayoría de los sistemas de computación de bajo costo no tienen un coprocesador matemático, o lo proporcionan sólo como una opción. Por ejemplo, el microprocesador 80486DX tiene un coprocesador matemático interno, en tanto que el 80486SX no. Estos sistemas de rendimiento reducido sustituyen hardware con software. Cada una de las funciones matemáticas se dividen en operaciones elementales binarias que se pueden manejar directamente dentro de la CPU. Si bien esto proporciona el mismo resultado, el tiempo de ejecución es mucho más lento, por ejemplo, en un factor de 10 a 20.


La mayoría de software en el ordenador, puede ser utilizado con o sin un coprocesador matemático. Esto se logra haciendo que el compilador genere código de máquina para manejar ambos casos, todos almacenados en el programa ejecutable final. Si un coprocesador matemático está presente en el equipo concreto que se está utilizando, una sección del código se ejecutará. Si un coprocesador matemático no está presente, la otra sección del código será utilizado. El compilador también puede ser dirigido para generar el código sólo para una de estas situaciones. Por ejemplo, usted de vez en cuando encontrará un programa que requiere que un coprocesador matemático esté presente, y se colgará cuando se ejecuta en un ordenador que no tiene uno. Aplicaciones tales como tratamiento de textos por lo general no gozan de un coprocesador matemático. Esto es debido a que implican modificar los datos en torno a la memoria, no el cálculo de expresiones matemáticas. Del mismo modo, los cálculos de punto fijo variables (enteras) no se ven afectadas por la presencia de un coprocesador matemático, ya que se manejan dentro de la CPU. Por otro lado, la velocidad de ejecución de DSP y otros programas computacionales usando cálculos de punto flotante pueden ser de un orden de magnitud diferente con y sin coprocesador matemático.

El procesador y la memoria principal están contenidos en chips separados en la mayoría de los sistemas informáticos. Por razones obvias, te gustaría que la memoria principal fuese muy grande y muy rápida. Lamentablemente, esto hace la memoria muy cara. La transferencia de datos entre la memoria principal y la CPU es un cuello de botella muy común en la velocidad. La CPU pide a la memoria principal la información binaria en una dirección de memoria y, a continuación, debe esperar a recibir la información. Una técnica común para sortear este problema es utilizar una memoria caché. Esta es una muy pequeña cantidad de memoria de alta velocidad utilizada como un amortiguador entre la CPU y la memoria principal. Unos cientos de kilo bytes es típico. Cuando la CPU pide a la memoria principal proporcionar datos binarios en determinada dirección, la electrónica digital de alta velocidad copia e una sección de la memoria principal en torno a esta dirección en la memoria caché. La próxima vez que la CPU pida a la memoria información, es muy probable que ya estén contenidos en la memoria caché, con lo que la recuperación será muy rápida. Esto se basa en el hecho de que los programas tienden a acceder a lugares de la memoria que son vecinos a los que se a accedido previamente. En la aplicación típica de ordenador personal, la adición de una memoria caché puede mejorar en general la velocidad por varias veces. La memoria caché puede estar en el mismo chip de la CPU, o bien puede ser un dispositivo electrónico externo.

La tasa a la cual los datos se pueden transferir entre subsistemas depende del número de líneas de datos paralelas previstas, y la tasa máxima que las señales digitales que se pueden transmitir a lo largo de cada línea. Los datos digitales en general, pueden ser transferidos a una tasa muy superior dentro de un chip, frente a la transferencia de datos entre chips. Asimismo, los caminos de datos que deben pasar a través de conectores eléctricos a otras placas de circuito impreso (es decir, la estructura de bus) Puede ser más lento todavía. Esta es una fuerte motivación para el relleno de tanta electrónica como sea posible dentro de la CPU.

Un problema especialmente desagradable para la velocidad del ordenador es la compatibilidad hacia atrás. Cuando una empresa de computadoras introduce un nuevo producto, digamos una tarjeta de adquisición de datos o un programa de software, quieren vender el producto en el mercado más grande posible. Esto significa que debe ser compatible con la mayoría de las computadoras actualmente en uso, que puede abarcar varias generaciones de tecnologías. Con frecuencia, esto limita el rendimiento del hardware o software a la de un sistema mucho más antigua. Por ejemplo, supongamos que usted compra una tarjeta I / O que se conecta en el bus de 200 MHz Pentium de su computadora personal, que le proporciona ocho líneas digitales que pueden transmitir y recibir datos de un byte a la vez. Así, puede escribir un programa en ensamblador para la rápida transferencia de datos entre el ordenador y algunos dispositivos externo, como un experimento científico o con otra computadora. Pero para su sorpresa, la máxima tasa de transferencia de datos es sólo unas 100,000 bytes por segundo, más de mil veces más lento que la tasa del reloj del microprocesador! El villano es el bus ISA, una tecnología que es compatible hacia atrás con las computadoras de los primeros 1980.

La Tabla 4-6 proporciona tiempos de ejecución para varias generaciones de ordenadores. Obviamente, debe tratar estas aproximaciones como muy toscas. Si quieres entender tu sistema, toma mediciones de tu sistema. Es muy fácil; escribir un bucle que ejecute una operación que ejecute un millón de alguna operación, y mirar el tiempo que tarda. Los tres primeros sistemas, el 80286, 80486 y Pentium, son el estándar de ordenadores de escritorio personales de 1986, 1993 y 1996, respectivamente. El cuarto es un microprocesador de 1994 diseñado especialmente para tareas de DSP, el Texas Instruments TMS320C40.



El Pentium es más rápido que el sistema 80286 por cuatro razones, (1) la mayor velocidad de reloj, (2) más líneas en el bus de datos, (3) la adición de una memoria caché, y (4) una mayor eficiencia interna de diseño, que requiere un menor número de ciclos de reloj por instrucción.

Si el Pentium era un Cadillac, el TMS320C40 sería un Ferrari: menos comodidad, pero velocidad cegadora. Este chip es representante de varios microprocesadores diseñados específicamente para reducir el tiempo de ejecución de algoritmos DSP. Otros en esta categoría son el i860 de Intel, AT & T DSP3210, Motorola DSP96002, y la Analog Devices ADSP-2171. A estos se les conoce por los nombres siguientes: microprocesador DSP, Digital Signal Processor, y RISC (Reduced Instruction Set Computer). Este último nombre refleja que el aumento de la velocidad se debe a un menor número de instrucciones de montaje nivel que se pone a disposición de los programadores. En comparación, los microprocesadores más tradicionales, como el Pentium, son llamados CISC (Complex Instruction Set Computer).

Los microprocesadores DSP se utilizan de dos formas: como módulos esclavos bajo el control de un ordenador más convencional, o como incrustados en un procesador dedicado, como un teléfono móvil. Algunos modelos sólo manejan números de punto fijo, mientras que otros pueden trabajar con punto flotante. La arquitectura interna utilizada para obtener la mayor velocidad incluye: (1) los lotes de la caché de memoria muy rápida que figuran en el chip, (2) buses separados para el programa y los datos, lo que permite el acceso a dos a la vez (llamada Arquitectura de Harvard), (3) hardware rápido para cálculos matemáticos que figuran directamente en el microprocesador, y (4) el diseño pipeline.

Una arquitectura pipelin (tubería) rompe el hardware necesario para una determinada tarea en varias etapas sucesivas. Por ejemplo, la adición de dos números puede hacerse en tres etapas pipeline. La primera etapa del pipeline, no hace nada pero obtiene los números a sumar de memoria. La única tarea de la segunda etapa consiste en añadir los números. La tercera etapa no hace nada, pero almacena el resultado en la memoria. Si cada etapa puede concluir su tarea en un solo ciclo de reloj, todo el procedimiento se llevará a tres ciclos de reloj para ejecutarse.

La característica fundamental de la estructura de tuberías es que otra tarea se puede iniciar antes de la anterior tarea esté finalizada. En este ejemplo, podría comenzar la adición de otros dos números tan pronto como la primera etapa esté inactiva, al final del primer ciclo de reloj. Para un gran número de operaciones, la velocidad del sistema se cita como una suma por ciclo de reloj, incluso aunque la adición de dos números cualesquiera requiere de tres ciclos de reloj para completarse. Los pipeline son de gran velocidad, pero pueden ser difíciles de programar. El algoritmo debe permitir el comienzo de un nuevo cálculo, a pesar de que los resultados de los cálculos anteriores no estén disponibles (ya que aún están en tramitación).

Velocidad de ejecución: Consejos de programación

Aunque computadoras y lenguajes de programación son importantes para maximizar la velocidad de ejecución, no son algo tu puedas cambiar de un día para otro. En comparación, la forma en que programas lo puedes cambiar en cualquier momento, y afecta de manera drástica al tiempo en que el programa requerirá para ejecutarse. He aquí tres sugerencias.

En primer lugar, utiliza variables enteras en lugar de punto flotante cuando sea posible. Los microprocesadores convencionales, como los utilizados en los ordenadores personales, procesan los enteros de 10 a 20 veces más rápido que los números de punto flotante. En los sistemas sin un coprocesador matemático, la diferencia puede ser de 200 a 1. Una excepción a esto es la división, que es a menudo realizada mediante la conversión de los valores en coma flotante. Esto hace la operación horriblemente lenta en comparación con otros cálculos enteros.

En segundo lugar, evita el uso de funciones como: sin(x), log(x), yx, etc. Estas trascendentales funciones se calculan como series de adiciones, sustracciones y multiplicaciones. Por ejemplo, las series de potencias de Maclaurin disponen:


Si bien estas relaciones son de longitud infinita, los términos se vuelven rápidamente lo suficientemente pequeños como para ser ignorados. Por ejemplo:


Estas funciones requieren alrededor de diez veces más tiempo para calcularse que una sola adición o multiplicación. Varios trucos se pueden utilizar para eludir estos cálculos, tales como: x3 =x ∙ x ∙ x; sin (x) = x, cuando x es muy pequeño; sin (-x) = -sin (x) , donde ya sabes uno de los valores y necesitas encontrar el otro, etc. La mayoría de los lenguajes sólo tienen unas pocas funciones trascendentales, y esperan que usted derive las demás por medio de las relaciones en la siguiente tabla. No es de extrañar, que estos cálculos que se derivan sean aún más lentos.



Otra opción es precalcular estas funciones lentas, y almacenar los valores en una tabla de búsqueda "look-up table" (LUT). Por ejemplo, imagine que un sistema de 8 bits de adquisición de datos utilizados para supervisar continuamente el voltaje a través de una resistencia. Si el parámetro de interés es la potencia que se disipa en la resistencia, la medición de voltaje se puede utilizar para calcular: P=V2 / R. Como alternativa más rápida, la potencia correspondiente a cada uno de las 256 posibles mediciones de voltaje se puede calcular de antemano, y se almacenan en una LUT. Cuando el sistema está en funcionamiento, la medición de tensión, un número digital entre 0 y 255, se convierte en un índice en la LUT para encontrar la correspondiente potencia. La búsqueda en tablas pueden ser cientos de veces más rápida que el cálculo directo.

En tercer lugar, aprende lo que es rápido y lo que es lento en su sistema en particular. Esto viene con la experiencia y las pruebas, y siempre habrá sorpresas. Presta especial atención a los comandos gráficos y I / O. Usualmente hay varias formas de manejar estos requisitos, y las velocidades pueden ser tremendamente distintas. Por ejemplo, el comando de BASIC: BLOAD, transfiere un archivo de datos directamente a una sección de la memoria. Al leer el mismo archivo en la memoria byte-por-byte (en un bucle) puede ser 100 veces más lento. Como otro ejemplo, el comando de BASIC: LINE, se puede utilizar para dibujar un cuadro de color en la pantalla de vídeo. Dibujar el mismo cuadro píxel por píxel puede ser 100 veces más lento. Incluso poner una declaración de impresión dentro de un bucle (para realizar un seguimiento de lo que está haciendo) puede demorar la operación por ¡miles de veces!

Texto basado en: "The Scientist and Engineer's Guide to Digital Signal Processing"
Steve Smith

Sinusoides, amplitud y frecuencia

Capítulo I (parte I)

La música electrónica se realiza, habitualmente, usando un ordenador, mediante la sintetización o procesamiento digital de señales de audio. Estas señales de audio, son secuencias de números,

Donde el índice n, el número de muestra, que varía en un rango entre algunos o todos los enteros. Un único número en la secuencia es llamado una muestra. Un ejemplo de una señal digital de audio es la sinusoide:


Donde a es la amplitud, ω es la frecuencia angular, y es la fase inicial. La fase es una función de el número de muestra n, igual a ωn + . La fase inicial es la fase en la muestra número cero (n = 0).

La figura 1.1 (parte a) muestra una sinusoide gráficamente. El eje horizontal muestra sucesivos valores de n y el eje vertical muestra los correspondientes valores de x [n]. El gráfico está dibujado de tal modo para enfatizar la naturaleza muestreada de la señal. Alternativamente, podríamos dibujarlo más sencillamente como una curva continua (parte b). el dibujo superior es la representación más fiel de la sinusoide de audio digital, mientras que el de abajo puede considerarse como la idealización de ella.

Las sinusoides tienen un papel principal en el procesamiento de audio, ya que, si mueves una de ellas hacia la izquierda o a la derecha por cualquier número de muestras, se obtiene otra. Esto hace fácil calcular el efecto de todo tipo de operaciones sobre las sinusoides. Nuestros oídos usan esta misma propiedad para ayudarnos a analizar sonidos, esta es la razón de que las sinusoides, y las combinaciones de sinusoides, puedan ser usadas para realizar muchos efectos musicales.

Las señales de audio digital no tienen ninguna relación intrínseca con el tiempo, pero para escucharlas debemos usar una tasa de muestreo, normalmente dada por la variable R, la cual es el número de muestras que entran en un segundo.


El tiempo t está relacionado con el número de muestras n por la relación Rt = n, o t = n/R. Una señal sinusoidal con frecuencia angular ω tiene una frecuencia tiempo-real igual a:

Medido en Hertzios (véase: ciclos por segundo), ya que un ciclo es 2π radianes y un segundo son R muestras. Una señal de audio del mundo real puede ser expresada como una variación temporal del voltaje o presión del aire. Tenemos que asumir que hay la suficiente precisión numérica para que podamos ignorar los errores de redondeo, y que el formato numérico es ilimitado en rango, para que las muestras tomen el valor adecuado. Sin embargo, la mayoría del hardware de audio digital sólo trabaja sobre un rango limitado de entrada y salida de valores, normalmente entre 1 y -1. el software moderno de procesamiento de audio normalmente usa una representación de punto flotante para las señales. Esto nos permite usar cualquier unidad que sea más conveniente para cada cuestión, aunque al final la salida de audio estará dentro del rango del hardware.


Basado en "The Theory and Technique of Electronic Music" Miller Puckette


Sobre el software DSP

Capítulo IV (parte II)

Prescisión numérca


Los errores asociados con la representación de números son muy similares a los errores de cuantificación durante la conversión analógico-digital. Tu quieres almacenar un rango continuo de valores, sin embargo, sólo puedes representar un número finito de niveles de cuantificación. Cada vez que un nuevo número es generado, después de un cálculo matemático, por ejemplo, debe ser redondeado al valor más cercano que pueda almacenarse en el formato que se está usando.

Como ejemplo, imagina que asignas 32 bits para almacenar un número. Puesto que hay exactamente 232 = 4,294,967,296 diferentes patrones de bits posibles, puedes representar exactamente 4,294,967,296 números diferentes. Algunos lenguajes de programación permiten una variable llamada de un entero largo (long integer), almacenado como 32 bits, punto fijo, complemento a dos.

Esto significa que los 4,294,967,296 patrones de bits posibles representan los enteros entre
-2,147,483,648 y 2,147,483,647. En comparación, la precisión de punto flotante simple extiende estos 4,294,967,296 patrones sobre una gama mucho más amplia: - 3.4 × 1038 a 3.4 × 1038

Con las variables de punto fijo, las diferencias entre los números adyacentes son siempre exactamente uno. En la notación de punto flotante, las diferencias entre los números adyacentes varían sobre el rango del número representado. Si queremos escoger al azar un número de punto flotante, la diferencia con el siguiente número es aproximadamente diez millones de veces más pequeña que el número en sí (para ser exactos, 2-24 a 2-23 veces el número). Este es un concepto clave de la notación de punto flotante: los números grandes tienen grandes diferencias entre ellos, mientras que los números pequeños tienen pequeñas diferencias. la siguiente Figura ilustra esto mostrando números consecutivos de punto flotante, y las diferencias que los separan.



El programa en el cuadro siguiente ilustra la forma como el error de redondeo (error de cuantificación en cálculos matemáticos) provoca problemas en DSP. Dentro del bucle del programa, dos números aleatorios se añaden a la variable de punto flotante X, y restados luego de nuevo. Idealmente, esto no debería hacer nada. En realidad, el error de redondeo de cada una de las operaciones aritméticas hace que el valor de X gradualmente se derive lejos de su valor inicial. Esta deriva puede tomar una de dos formas dependiendo de cómo los errores se unan. Si los errores de redondeo al azar son positivos y negativos, el valor de la variable aleatoriamente aumentará y disminuirá. Si los errores son predominantemente del mismo signo, el valor de la variable se alejará mucho más rápidamente y de manera uniforme.


La siguiente figura muestra cómo la variable, X, en este programa de ejemplo, deriva en valor. Una preocupación evidente es que el error aditivo es mucho peor que el error aleatorio. Esto es debido a que los errores aleatorios tienden a anularse entre sí, mientras que el aditivo se dedica a acumular errores. El error aditivo es aproximadamente igual al error de redondeo de una sola operación, multiplicado por el número total de las operaciones. En comparación, el error aleatorio sólo aumenta de forma proporcional a la raíz cuadrada del número de operaciones. Como muestra este ejemplo, el error aditivo puede ser cientos de veces peor que el error aleatorio para los algoritmos comunes de DSP.



Lamentablemente, es casi imposible de controlar o predecir cuál de los dos comportamientos de un determinado algoritmo nos cabe esperar. Por ejemplo, en el programa anterior se genera un error aditivo. Esto puede ser cambiado por un error aleatorio limitándonos a hacer una ligera modificación en los números que se van sumar y restar. En particular, la curva de error aleatorio generada por la definición: A =EXP (RND) y B = EXP (RND), en lugar de: A= RND y B =RND. En lugar de A y B siendo distribuidos aleatoriamente entre los números 0 y 1, se vuelven valores exponencialmente distribuidos entre 1 y 2.718. Incluso este pequeño cambio es suficiente para cambiar el modo de acumulación de errores.

Dado que no podemos controlar la forma en que los errores de redondeo se acumulan, ten en cuenta el peor escenario posible. Esperamos que cada número de precisión simple tendrá un error de aproximadamente una parte en cuarenta millones, multiplicado por el número de operaciones que hayan tenido lugar a través de él. Esto se basa en el supuesto de error aditivo, y el error medio de una sola operación es de una cuarta parte del nivel de cuantificación. A través del mismo análisis, cada número de doble precisión tiene un margen de error de aproximadamente una parte de cuarenta cuatrillones, multiplicada por el número de operaciones.

La tabla 4-2 ilustra un particularmente molesto problema de error de redondeo. Cada uno de los dos programas en esta tabla realiza la misma tarea: la impresión de 1001 números igualmente espaciados entre 0 y 10. La parte izquierda del programa utiliza la variable de punto flotante, X, como el índice de bucle. Cuando se encarga la ejecución del bucle, el ordenador comienza por establecer el índice de la variable a partir del valor del bucle (0 en este ejemplo). Al final de cada ciclo de bucle, el tamaño del paso (0,01 en el caso), se añadirá al índice. Una decisión se toma entonces: ¿hay más ciclos de bucles necesarios, o se completó el bucle? El bucle se termina cuando el computador considera que el valor del índice es mayor que el valor de terminación (en este ejemplo, 10,0). Como demuestra la salida generada, el error de redondeo en las adiciones causa que el valor de X acumule una discrepancia importante en el curso del bucle. De hecho, el error acumulado impide la ejecución del último ciclo de bucle. En lugar de que X tenga un valor de 10.0 en el último ciclo, el error hace que el último valor de X sea igual a 10.000133. Dado que X es mayor que el valor de terminación, el ordenador piensa que su labor está hecha, y termina el bucle prematuramente. Este último valor desaparecido es un error común en muchos programas de ordenador.


En comparación, el programa de la derecha utiliza una variable entera, I%, para controlar el bucle. La suma, resta, o multiplicación de dos enteros siempre produce otro entero. Esto significa que la notación de punto fijo no tiene absolutamente ningún error de redondeo con estas operaciones. Los enteros son ideales para el control de bucles, así como otras variables que se someten a múltiples operaciones matemáticas. ¡El último ciclo del bucle está garantizado que se ejecutará! A menos que tenga una fuerte motivación para hacer otra cosa, siempre use enteros para índices de bucle y contadores.

Si es necesario usar una variable de punto flotante como un índice de bucle, trata de usar fracciones, que sean una potencia de dos (como por ejemplo: 1/2, 1/4, 3/8, 27/16), en lugar de una potencia de diez (Por ejemplo: 0.1, 0.6, 1.4, 2.3, etc.) Por ejemplo, sería mejor utilizar: FOR X = 1 TO 10 STEP 0.125, en lugar de: FOR X = 1 a 10 STEP 0.1. Esto permite que el índice siempre de una representación binaria exacta, reduciendo así el error de redondeo. Por ejemplo, el número decimal: 1.125, se puede representar exactamente en la notación binaria: 1.001000000000000000000000 × 20. En comparación, el número decimal: 1.1, se halla entre dos números de punto flotante: 1.0999999046 y 1.1000000238 (en binario estos números son: 1,00011001100110011001100× 20 y 1,00011001100110011001101 × 20). Esto resulta en un error inherente cada vez que 1.1 se encuentra en un programa.

Un hecho útil para recordar: la precisión de punto flotante simple tiene una representación binaria exacta para cada número completo entre ± 16,8 millones (para ser exactos, ± 224). Por encima de este valor, las diferencias entre los niveles son más grandes que uno, causando que algunos conjuntos de valores número se puedan perder. Esto permite que los números de punto flotante en su conjunto (entre ± 16,8 millones), puedan ser sumados, restados y multiplicados, sin error de redondeo.

Velocidad de ejecución

La programación DSP puede ser vagamente dividida en tres niveles de complejidad: Ensamblador, Compilado, y de aplicación específica. Para entender la diferencia entre estas tres, tenemos que empezar con los conceptos básicos de la electrónica digital. Todos los microprocesadores se basan en torno a un conjunto de registros binarios internos, es decir, un grupo de flip-flops que pueden almacenar una serie de unos y ceros. Por ejemplo, en el microprocesador 8088, el núcleo del original IBM PC, cuenta con cuatro registros de propósito general, cada una de ellos de 16 bits. Estos se identifican por los nombres: AX, BX, CX y DX. También hay otros nueve registros con fines especiales, llamados: SI, DI, SP, BP, CS, DS, SS, ES, e IP. Por ejemplo, IP, es el Puntero de Instrucción, donde reside en la memoria la próxima instrucción.

Supongamos que escribimos un programa para sumar los números: 1234 y 4321. Cuando comienza el programa, IP contiene la dirección de una sección de memoria que contiene un patrón de unos y ceros, como se muestra en la siguiente tabla. Aunque parece sin sentido para la mayoría de los seres humanos, este patrón de unos y ceros contiene todos los comandos y los datos necesarios para completar la tarea. Por ejemplo, cuando el microprocesador encuentra el patrón de bits: 00000011 11000011, lo interpreta como un comando para tomar los 16 bits almacenados en el registro BX, y sumarlos en binario a los 16 bits almacenado en el registro AX, y almacenar el resultado en el registro AX. Este nivel de programación se denomina código máquina, y no es más que un trabajo un pelo por encima de los circuitos electrónicos reales.


Como trabajar en binario conduce eventualmente hasta a los más pacientes ingenieros a la locura, a estos patrones de unos y ceros se les asignan nombres de acuerdo a la función que realizan. A este nivel de la programación se le llama ensamblador, y un ejemplo se muestra en la siguiente Tabla. Aunque un programa en ensamblador es mucho más fácil de entender, es fundamentalmente lo mismo que la programación en código de máquina, ya que hay una correspondencia uno-a-uno entre los comandos del programa y las acciones adoptadas en el microprocesador.


Por ejemplo: ADD AX, BX se traduce a: 00000011 11000011. Un programa llamado ensamblador se usa para convertir el código ensamblador (llamado el código fuente) en los patrones de unos y ceros (llamados el código objeto o código ejecutable). Este código ejecutable se puede ejecutar directamente en el microprocesador. Evidentemente, la programación de ensamblador requiere un amplio conocimiento de la construcción interna de un particular microprocesador que se pretende utilizar.

La programación de ensamblador implica la manipulación directa de la electrónica digital: registros, localizaciones de memoria, estado de los bits, etc. El siguiente nivel de sofisticación puede manipular variables abstractas sin referencia alguna al hardware particular. Estos se llaman compilados o lenguajes de alto nivel. Una docena o así son de uso común, tales como: C, BASIC, FORTRAN, PASCAL, APL, COBOL, LISP, etc. La siguiente tabla muestra un programa BASIC para añadir 1234 y 4321. El programador sólo conoce las variables A, B y C, y nada sobre el hardware.

Un programa llamado compilador se utiliza para transformar el código fuente de alto nivel directamente a código de máquina. Esto requiere que el compilador asigne los lugares de memoria del hardware a cada una de las variables abstractas que son referenciadas. Por ejemplo, la primera vez que el compilador encuentra la variable A (línea 100), entiende que el programador está utilizando este símbolo en el sentido de una variable de punto flotante de simple precisión. En consecuencia, el compilador designa cuatro bytes de memoria que se utilizarán para nada más que para contener el valor de esta variable. Cada vez que aparece una A en el programa, la computadora sabe que tiene que actualizar el valor de los cuatro bytes como sea necesario. El compilador también rompe complicadas expresiones matemáticas, tales como: Y = LOG (XCOS (Z)), en aritmética más básica. Los microprocesadores sólo saben sumar, restar, multiplicar y dividir. Cualquier cosa que sea más complicada se debe hacer como una serie de estas cuatro operaciones elementales.

Los lenguajes de alto nivel aíslan al programador del hardware. Esto hace la programación mucho más fácil y permite que el código fuente pueda ser transportado entre los diferentes tipos de microprocesadores. Lo que es más importante, el programador que utiliza un lenguaje compilado no necesita saber nada acerca del funcionamiento interno de la computadora. Otro programador ha asumido esta responsabilidad, el que escribió el compilador.

La mayoría de los compiladores funcionan mediante la conversión de todo el programa en código de máquina antes de que sea ejecutado. Una excepción a esto es un tipo de compilador llamado intérprete, de los cuales el intérprete BASIC es el ejemplo más común. Un intérprete convierte una sola línea de código fuente en código de máquina, ejecuta el código máquina, y luego pasa a la siguiente línea de código fuente. Esto proporciona un entorno interactivo para programas simples, aunque la velocidad de ejecución es muy lenta (piensa en un factor de 100).

El nivel más alto de sofisticación de programación se encuentra en paquetes de aplicaciones para DSP. Estos vienen en una variedad de formas, y con frecuencia se proporcionan apoyo a hardware específico. Suponga que usted compra un nuevo microprocesador desarrollado de DSP a integrar en su proyecto actual. Estos dispositivos suelen tener muchas capacidades y características de DSP: entradas analógicas, salidas analógicas, E/S digital, filtros antialiasing y de reconstrucción, etc. La pregunta es: ¿cómo se programa? En el peor de los casos, el fabricante le dará un ensamblador, y esperará que tu aprendas la arquitectura interna del dispositivo. En un caso típico, te darán un compilador de C, lo que le permite el programar sin ser molestado por la forma en que opera el microprocesador.

En el mejor de los casos, el fabricante proporcionará un sofisticado paquete de software para ayudarte en la programación: las bibliotecas de algoritmos, rutinas de E/S preescritas, herramientas de depuración, etc. Puede que simplemente conectes iconos de forma deseada en un sistema fácil de utilizar de pantalla gráfica. Las cosas que manipular son vías de señales, algoritmos de procesamiento de señales analógicas, parámetros E/S, etc. Cuando estés satisfecho con el diseño, es transformado en código de máquina adecuado para la ejecución en el hardware. Otros tipos de paquetes de aplicaciones se utilizan con el procesamiento de imágenes, análisis espectral, instrumentación y control, diseño de filtros digitales, etc. Esta es la forma del futuro.

La distinción entre estos tres niveles puede ser muy difusa. Por ejemplo, la mayoría de los lenguajes compilados te permiten manipular el hardware directamente. Asimismo, un lenguaje de alto nivel con una rica biblioteca de funciones DSP está muy cerca de ser un paquete de aplicaciones. El punto de estas tres categorías es entender lo que estás manipulando: (1) hardware, (2) variables abstractas, o (3) los procedimientos completos y los algoritmos.

Existe también otro concepto importante detrás de estas clasificaciones. Al utilizar un lenguaje de alto nivel, te basas en el programador que escribió el compilador para entender las mejores técnicas para la manipulación del hardware. De igual modo, al utilizar un paquete de aplicaciones, te basas en el programador que escribió el paquete para entender mejor las técnicas DSP . Aquí está el problema: los programadores no han visto el problema particular que tu estás tratando. Por lo tanto, no siempre pueden proporcionarte una solución óptima. A medida que se opera a un nivel más alto, esperamos que el código máquina final será menos eficiente en términos de uso de memoria, velocidad y precisión.

¿Qué lenguaje de programación debo usar? Eso depende de quién eres y de lo que quieras hacer. La mayoría de los científicos y programadores de computadoras usan C (o el más avanzados de C + +). Potencia, flexibilidad, modularidad; C lo tiene todo. C es tan popular, que la pregunta se convierte en: ¿Por qué alguien programaría su aplicación DSP en algo distinto de C? Tres respuestas me vienen a la mente. En primer lugar, DSP ha crecido tan rápidamente que algunas organizaciones y personas están bloqueadas en el modo de otros idiomas, tales como FORTRAN y PASCAL. Esto es especialmente cierto en el caso de militares y agencias gubernamentales que son notoriamente lentas para cambiar. En segundo lugar, algunas aplicaciones requieren la máxima eficiencia, sólo alcanzable por la programación en ensamblador. Este cae en la categoría de "un poco más de velocidad por mucho más trabajo." En tercer lugar, C en particular no es un idioma fácil de dominar, especialmente para los programadores a tiempo parcial. Esto incluye una amplia gama de ingenieros y científicos que necesitan de vez en cuando técnicas DSP para ayudar en sus actividades de investigación o de diseño. Este grupo a menudo se convierte a BASIC, debido a su sencillez.

Comparar la velocidad de ejecución de hardware o software, es una tarea ingrata, no importa cual sea el resultado, el perdedor tendrá que llorar ¡el partido es injusto! Los programadores a los que les gustan los lenguajes de alto nivel (como los informáticos tradicionales ), sostienen que el ensamblador es sólo un 50% más rápido que el código compilado, pero cinco veces más problemático. Los que les gusta el ensamblador (por lo general, los científicos y los ingenieros de hardware) se refieren a la inversa: el montaje es cinco veces más rápido, pero sólo el 50% más difícil de usar. Como en la mayoría de las controversias, ambas partes pueden proporcionar datos selectivos en apoyo de sus reivindicaciones.

Como regla práctica, cabe esperae que una subrutina escrita en ensamblador sea de entre 1.5 y 3.0 veces más rápida que un programa comparable de alto nivel. La única manera de saber el valor exacto es escribir el código y realizar pruebas de velocidad. Como los ordenadores personales van en aumento en la velocidad de alrededor de 40% cada año, la escritura de una rutina en ensamblador es equivalente a un salto de alrededor de un período de dos años en la tecnología de hardware.

La mayoría de los programadores profesionales se ven bastante ofendidos ante la idea de utilizar ensamblador, y lo mismo con BASIC. Su razón es bastante simple: BASIC y ensamblador no permiten el uso de buenas prácticas de software. Buen código debe ser potable (capaz de pasar de un tipo de ordenador a otro), modular (roto en una estructura bien definida de subrutinas), y fáciles de entender (con muchas observaciones y descriptivos nombres de variables). La débil estructura de ensamblador y de BASIC dificulta el logro de estas normas. Esto se complica por el hecho de que las personas que se sienten atraídas por ensamblador y BASIC a menudo tienen poco entrenamiento formal en la estructura adecuada de software y documentación.

Los amantes de ensamblador responden a este ataque con uno propio. Supongamos que escribes un programa en C, y tu competidor escribe el mismo programa en ensamblador. El usuario final tendrá la primera impresión de que tu programa es basura, porque es dos veces más lento. Nadie sugeriría que escribas grandes programas en ensamblador, sólo las partes del programa que requieren una rápida ejecución. Por ejemplo, muchas funciones en las bibliotecas de software DSP están escritas en ensamblador, y luego se accede desde programas más amplio escritos en C. Incluso los más firmes puristas utilizarán software en código ensamblador, siempre y cuando no lo tengan que escribir.

Texto basado en: "The Scientist and Engineer's Guide to Digital Signal Processing"
Steve Smith

lunes, 2 de marzo de 2009

Como funciona un efecto Chorus

Así como un coro es un grupo de cantantes, el efecto coro puede hacer que un instrumento único suene como sí hubiera varios instrumentos que se estén reproduciendo. Añade espesor al sonido, y es a menudo descrito como «exuberante» o «rico».

Como funciona

El algoritmo detrás del efecto coro no es un espectacular o sorprendente truco - es en realidad bastante simple. ¿Qué sucede cuando dos personas tocan instrumentos al unísono? Bueno no siempre están tocando con una sincronización precisa, así que hay cierto retraso entre los sonidos que producen. Además, el tono de los dos instrumentos puede desviarse un poco, a pesar de una cuidadosa afinación. Estas son las funciones que reproduce tu efecto de coro.

Este pequeño retraso puede ser fácilmente aplicado con una línea de retardo. Sin embargo, el efecto de “desafine” puede no ser tan simple, pero se puede lograr mediante la transformación de la simple línea de retardo en una línea de retardo de longitud variable. La parte "longitud variable" sólo significa que el tiempo de retardo cambia a lo largo del tiempo, sin embargo este efecto sobre el tono puede no ser muy claro en un primer momento.

Para comprender la forma en que el tono es cambiado, dibujaremos el delay como un dispositivo de grabación. Este almacena una copia exacta de la señal de entrada tal como llega, como una grabadora de casete, y luego sale de ella un poco más tarde, a la misma velocidad (tasa). Para aumentar la cantidad retraso, querremos un segmento mayor de señal almacenada en el delay antes de que se reproduzca. Para hacer esto, leemos fuera de la línea de retardo a una velocidad menor de la que ha sido escrita (la tasa grabación no se modifica, por lo que mayor cantidad de señal se almacena). Volver a leer a una velocidad menor es como arrastrar tus dedos sobre la rueda del casete, que sabemos que disminuye el tono. Del mismo modo, para reducir el tiempo de retardo, podemos leer más rápido, análogo a la aceleración de un cassette, lo que aumenta el tono - el "efecto munchkin".

Así que ahora, mediante la mezcla de la señal original, la retrasada y la modulada en tono, tenemos el efecto coro.


Esta estructura te puede parecer muy familiar - es básicamente nuestro amigo el flanger. El chorus difiere en sólo un par de detalles. Una diferencia es la cantidad de delay que se utiliza. Los tiempos de retardo en un chorus son más grandes que en un flanger, por lo general entre el 20 ms. y 30 ms. (el retraso del flanger por lo general oscila entre 1 ms. a 10 ms.) Este retraso ya no produce el característico sonido de barrido del flanger. El coro también difiere de flanger en que en general no se utiliza feedback.

El único punto a discutir es la forma en que el tiempo de retardo cambia. En general, son usadas algunas formas de onda periódicas, como la onda senoidal. Estas formas de onda cambian poco a poco (digamos que a 3 Hz y menos.) Y son conocidas como un LFO (oscilador de baja frecuencia). Puedes controlar el sonido de chorus cambiando la forma, la frecuencia y la amplitud de la forma de onda. Hacemos un simple cambio en nuestro diagrama del chorus para denotar esta dependencia LFO.



Otras variaciones sobre el efecto chorus también son posibles. Por ejemplo, en lugar de utilizar un LFO, se pueden usar cambios de tiempo de retardo al azar, lo que podría modelar (imitar) músicos tocando al unísono un poco mejor. Además, cuando se toca al unísono, habrá algunas diferencias de volumen entre los intérpretes, por lo que también podría variar la amplitud de la señal retrasada. Este parámetro de amplitud podría ser controlado por otro LFO.

Parámetros comunes:

Delay

El parámetro delay simplemente controla la cantidad de retraso utilizado. Más concretamente, controla el tiempo mínimo de retardo que se utiliza. Cuando el retraso se vuelve muy pequeño, el chorus actuará como un flanger. Tiempos de retardo típicos oscilan entre 20 y 30 ms.

Anchura y profundidad de barrido

La profundidad de barrido controla la cantidad total de tiempo de retardo que cambia en el tiempo. Por lo general se expresa en milisegundos, y la suma de la profundidad de barrido y parámetros de retraso es el máximo delay utilizado en el procesamiento de la señal. Alternativamente, puedes pensar en la profundidad de barrido como la amplitud del LFO. La relación entre los parámetros de retraso y profundidad de barrido se muestra en el siguiente gráfico.


La profundidad de barrido también aumenta la modulación de tono introducida por la línea de retardo variable en el tiempo. Esto sucede porque tienes que leer aún más rápido y más lento para cubrir el cambio total en el tiempo. Grandes profundidades de barrido crean un “gorgorito”.

La forma de onda del LFO

La forma de onda LFO muestra cómo el delay cambia a lo largo del tiempo. Cuando la onda alcanza un máximo, entonces el retraso está en su mayor valor. Cuando la forma de onda (y el total de tiempo de retardo) se incrementa, el tono se vuelve más bajo. Algunas formas de onda comúnmente utilizadas en el LFO se muestran en el gráfico.



La cantidad de la modulación de tono introducida por el chorus tiene que ver con la rapidez con que la forma de onda LFO cambia - la parte más empinada en la onda produce una gran cantidad de modulación de tono, mientras que las porciones relativamente planas tienen muy poco o ningún efecto sobre el tono. Podemos utilizar esta perspectiva para comprender la forma en que la profundidad de barrido varía el tono. Si aumentas la profundidad de barrido, estás estirando efectivamente la forma de onda verticalmente, lo cual la hace más empinada, y por lo tanto, el tono se altera más.

El seno es una función muy suave, y es siempre cambiante por lo que el tono está cambiando constantemente también. El triángulo por otra parte sólo produce dos tonos, porque la pendiente sólo toma dos valores diferentes, y el cambio entre los tonos es repentino. La forma de onda logarítmica es suave sobre su ciclo, pero hay un salto al final de cada ciclo. Dado que la pendiente al principio y al final de la forma de onda son diferentes, hay un cambio brusco en el tono también.

Velocidad / Tasa

El control de la velocidad es bastante sencillo. Este parámetro se refiere a la velocidad (tasa) a la que la forma de onda LFO se repite, y también es un factor en la modulación del tono. El aumento de la tasa es equivalente a la compresión de la forma de onda LFO en el tiempo, lo que lo hace más empinada, resultando en una mayor modulación de tono.

Número de voces

Hasta este punto, sólo hemos cubierto lo que se conoce como el chorus de una sola voz, lo que significa que sólo hay una copia de la entrada. Pero no hay razón por la cual no se puedan tener varias copias del sonido, modelando una situación con más de dos instrumentos siendo tocados. Algunas unidades chorus te permiten hacer precisamente esto, y puede que incluso te permitan elegir el número de voces a usar.

Normalmente, un chorus de múltiples voces utiliza una única forma de onda LFO para todas las voces, pero cada voz tiene una fase diferente. Esto significa que en cualquier momento, cada voz se encuentra en un punto diferente a lo largo de la forma de onda, por lo que tienen diferentes tiempos de retraso. Si todas las voces estuvieran en fase, se tendría el mismo efecto que un chorus con una sola voz con una mayor amplitud. Por supuesto, es posible construir un coro de tal forma que cada voz use su propia forma LFO, e incluso su propia tasa.




Implementaciones

Analógica

Los tiempos de retraso tan cortos que se necesitan hacen que los delays basados en cinta no sean prácticos, de modo se usan circuitos electrónicos en su lugar. Circuitos Sample-and-hold o “beckett-brigade” son utilizados. Estos son dispositivos que mantienen un voltaje (producido por la señal de entrada) durante una pequeña cantidad de tiempo. Múltiples etapas de estos circuitos pueden ser unidas juntas en series para producir el tiempo de delay deseado.

Digital

Los retrasos son fáciles de aplicar en el dominio digital como buffers circulares. Básicamente, tomas un fragmento de memoria, y secuencialmente lees y escribes valores de cada período de muestreo, incrementando a la siguiente ubicación de memoria para cada muestra. Sin embargo, de línea de retardo variables en el tiempo requieren un poco más de trabajo. El tiempo cambiante de retardo requiere tiempos de retardo que no sean múltiplos enteros del período de muestreo (y la señal de entrada está siendo muestreada a múltiplos de este período de muestreo). Por lo tanto, es a menudo el caso que queremos estimar el valor de la señal entre dos de los valores que tenemos almacenados. Con los valores de estos dos vecinos, podemos hacer una conjetura de cual es el valor deseado, y a este proceso se le llama interpolación. Uno de estos métodos, la interpolación lineal, es implementado conectando los dos valores conocidos por una línea recta y, a continuación, buscando en el valor de esta línea en algún momento que corresponda al tiempo de retardo deseado. La interpolación lineal es una solución muy simple, pero puede introducir ruido, así que otros métodos de interpolación pueden usarse.

Chorus estéreo

Un coro estéreo es generalmente construido ejecutando dos coros mono fónicos ejecutándose en fase de cuadratura. Esto simplemente significa que el LFO en cada flanger mono fónico difiere en la fase en noventa grados (o una cuarta parte de una longitud de onda). Esta técnica crea un sonido 'más amplio' porque el sonido que llega a cada uno de nuestros oídos es diferente.

Ten en cuenta que en un chorus de múltiples voces (donde cada voz es mono fónica), puedes crear un chorus estéreo simplemente "paneando" las voces lejos del centro de la mezcla.

Hijo del flanger

Como se mencionó antes, el chorus es esencialmente el mismo algoritmo que el flanger, pero el flager se caracteriza por unas longitudes de delay menores (de 1 ms a 10 ms) y una línea de retroalimentación que enruta la línea de delay de salida de vuelta a la entrada.

Basado en artículo: http://www.harmony-central.com/Effects/Articles/Chorus/

Sobre el software DSP

Capítulo IV (parte I)

Las aplicaciones DSP (digital signal processing) suelen ser programadas en los mismos lenguajes que cualquier otra tarea de ciencia e ingeniería, tales como: C, BASIC y ensamblador. El poder y la versatilidad de C hacen que sea el lenguaje elegido por científicos e informáticos y otros programadores profesionales. Por otro lado, la simplicidad de Basic lo hace ideal para los científicos e ingenieros que sólo de vez en cuando visitan el mundo de la programación. Independientemente de la lengua que se utilice, la mayoría de los problemas importantes de software DSP están enterrados muy por debajo en el ámbito de los unos y ceros. Estos incluye temas como: cuantos números son representados por patrones de bits, el error de redondeo en la aritmética del ordenador, la velocidad de cálculo de los diferentes tipos de procesadores, etc.

Las computadoras digitales son muy competentes almacenando y recordando los números; por desgracia, este proceso no está exento de errores. Por ejemplo, si le damos la instrucción a nuestro equipo de que guarde el número: 1,41421356. El equipo lo hará mejor que pueda, y almacenará el número más cercano que pueda representar: 1,41421354. En algunos casos este error es bastante insignificante, mientras que en otros casos, es desastroso. Como otro ejemplo, un clásico error de cálculo resulta de la adición de dos números con valores muy diferentes, por ejemplo, 1 y 0,00000001. Nos gustaría que la respuesta fuera 1,00000001, pero la respuesta del ordenador es 1. La comprensión de cómo las computadoras almacenan y manipulan los números te permite anticipar y corregir estos problemas antes de que tu programa escupa datos sin sentido.

Estos problemas se deben a que siempre un número fijo de bits son asignados para almacenar cada número, por lo general de 8, 16, 32 o 64. Por ejemplo, considera el caso de que se utilicen ocho bits para almacenar el valor de una variable. Debido a que hay 28 = 256 posibles patrones de bits, la variable sólo puede tomar 256 valores diferentes. Esta es una limitación fundamental de la situación, y no hay nada que se pueda hacer al respecto. La parte que podemos controlar es que valor declaramos que cada patrón de bit represente. En los casos más sencillos, los patrones de 256 bits puede representar los enteros de 0 a 255, 1 a 256, -127 a 128, etc. En un esquema más inusual, los patrones de 256 bits pueden representar 256 números exponencialmente relacionados: 1, 10, 100, 1000, …, 10254, 10255. Cada acceso a los datos debe comprender el valor que cada patrón de bit representa. Esto generalmente es proporcionado por un algoritmo o fórmula para la conversión entre el valor representado y el correspondiente patrón de bits, y viceversa.

Aunque hay muchos esquemas de codificación posibles, sólo dos se han convertido en formatos comunes, el punto fijo (también llamados números enteros) y punto flotante (también llamados números reales).

Punto fijo (enteros)

La representación de punto fijo se utiliza para almacenar números enteros, tanto positivos como negativos: …-3, -2, -1, 0, 1, 2, 3, …. Los programas de alto nivel, tales como C y BASIC, por regla general destinan 16 bits para almacenar cada entero. En el caso más simple, los 216 = 65,536 posibles patrones de bits son asignados a los números de 0 a 65535. A esto se le llama formato entero sin signo (unsigned integer). La conversión entre el patrón de bit y el número que se representa no es más que el cambio entre la base 2 (binario) y la base 10 (decimal). La desventaja del entero sin signo es que los números negativos no pueden ser representados.

El formato Offset binario es similar al entero sin signo, excepto en que los valores decimales se desplazan para permitir los números negativos. De esta manera, una representación de 16 bits utiliza 32,767 como compensación, lo que resulta en un rango entre -32,767 y 32,768. El offset binario no es un formato normalizado, y encontrarás otras compensaciones utilizadas, por ejemplo, 32,768. El uso más importante del offset en binario es en la conversión analógica a digital y en su inversa digital a analógica. Por ejemplo, la gama de voltajes de entrada de -5v a 5v puede ser asignada a los números digitales de 0 a 4095, para una conversión de 12 bits.

Signo y magnitud es otra forma muy sencilla de representar números negativos. El bit del extremo izquierdo se denomina bit de signo, y es un cero para números positivos, y un uno para los números negativos. Los otros bits son un estándar de representación binaria del valor absoluto del número. Esto se traduce en un patrón de bit que derrocha, ya que existen dos representaciones para el cero, 0000 (cero positivo) y 1000 (cero negativo). Este esquema de codificación, en números de 16 bits, tiene un rango de -32767 a 32767.

Estas tres primeras representaciones son conceptualmente simples, pero difíciles de implementar en hardware. Recuerda que cuando A = B + C se introduce en un programa de ordenador, algún ingeniero de hardware tiene que buscar la manera de hacer que el patrón de bits que representa a B, se combine con el patrón de bits que representa a C, para formar el patrón de bits que represente a A.

El complemento a dos (Two's complement) es el formato más querido por los ingenieros de hardware, y es la manera cómo los enteros suelen estar representados en las computadoras. Su funcionamiento es análogo al odómetro (aparato que mide la distancia recorrida por un coche) en un automóvil nuevo. Si se conduce hacia adelante cambia: 00000, 00001, 00002, 00003, y así sucesivamente. Cuando marcha hacia atrás, el odómetro cambia: 00000, 99999, 99998, 99997, etc.

Usando 16 bits, complemento a dos puede representar números de -32,768 a 32,767. La mayoría de los bits de la izquierda son 0 si el número es positivo o cero, y 1 si el número es negativo. En consecuencia, la mayoría de los bits de la izquierda, se llama el bit de signo, al igual que en la representación signo y magnitud. La conversión entre decimal y el complemento a dos es sencilla para los números positivos, una simple conversión de binario a decimal. Para los números negativos, el siguiente algoritmo se utiliza a menudo: (1) tomar el valor absoluto del número decimal, (2) convertirlo a binario, (3) complementar todos los bits (unos se hacen ceros y ceros se hacen unos), ( 4) sumar 1 al número binario. Por ejemplo: -5 →5 →0101→ 1010 → 1011. El formato complemento a dos es difícil para los seres humanos, pero fácil para la electrónica digital.

Punto flotante (números reales)

El esquema de codificación de los números de punto flotante es más complicado que el esquema de punto fijo. La idea básica es la misma que la utilizada en la notación científica, donde una mantisa se multiplica por diez elevada a algún exponente. Por ejemplo, 5,4321 × 106, donde 5.4321 es la mantisa y 6 es el exponente. La notación científica es excepcional para representar cantidades muy grandes o muy pequeñas . Por ejemplo: 1.2 × 1050, es el número de átomos en la tierra, o 2.6 × 10-23, la distancia que una tortuga rastrea en un segundo, en comparación con el diámetro de nuestra galaxia. Observe que los números representados en notación científica se normalizan de manera que exista un solo dígito distinto de cero a la izquierda de la coma decimal. Esto se logra ajustando el exponente según se necesite.

La representación de punto flotante es similar a la notación científica, con excepción de que todo se lleva a cabo en base dos, en lugar de en base diez. Si bien varios formatos similares se encuentran en uso, el más común es ANSI / IEEE Std. 754-1985. Esta norma define el formato de 32 bits para los números llamados de precisión única (single precision), así como los de 64 bits llamados de doble precisión. Como se muestra en el siguiente gráfico, Los 32 bits utilizados en la precisión doble se dividen en tres grupos diferentes: los bits de 0 a 22 forman la mantisa, los bits de 23 a 30 forman el exponente, y el bit 31 es el bit de signo. Estos bits forman el número de punto flotante, v, por la siguiente relación:


El término: (-1)S, simplemente significa que el bit de signo, S, es 0 para un número positivo y 1 para un número negativo. La variable, E, es el número entre 0 y 255 representado por los ocho bits del exponente. Restando 127 de este número permite al exponente correr de 2 elevado a-127 a 2 elevadoa a128. En otras palabras, el exponente se almacena en binario compensado (offset) con una compensación de 127.

La mantisa, M , es formada desde los 23 bits como una fracción binaria. Por ejemplo, la fracción decimal: 2,783, se interpreta: 2+ 7 / 10+ 8 / 100+ 3 / 1000. La fracción binaria: 1,0101, significa: 1+ 0 / 2+ 1 / 4+ 0 / 8+1 / 16. Los números de punto flotante son normalizados de la misma manera que en la notación científica, es decir, sólo hay un dígito distinto de cero a la izquierda de la coma decimal (llamado punto binario en base 2).





Dado que el único número distinto de cero que existe en base de dos es 1, el dígito principal en la mantisa siempre será un 1, y, por tanto, no necesita ser almacenado. La eliminación de esta redundancia permite disponer al número de un adicional bit de precisión. Los 23 bits almacenados, a la que se refiere la notación: m 22 , m 21 ,...,m 0 , forma mantisa de acuerdo a:



En otras palabras, M = 1+ m 22 2 -1 + m 21 2 -2 + m 20 2 -3 … Si los bits de 0 a 22 son todos ceros, M toma el valor de uno. Si los bits de 0 y 22 son todos unos, M está sólo un pelo por debajo de dos, es decir, 2 -2 -23 .

Usando este esquema de codificación, el número mas grande que puede ser representado es: ±(2-2 -23 ) × 2 128 = ± 6.8 × 10 38 . Por lo tanto el número más pequeño que puede ser representado es: ±1.0 × 2 -127 = ± 5.9 × 10 -39 .

El estándar IEEE reduce este rango ligeramente para liberar un patrón de bits y que se les pueda asignar un significado especial. En particular, el más grande y el más pequeño de los números permitidos en el estándar son ± 3.4 × 10 38 y ± 1.2 × 10 -38 , respectivamente. Este patrón de bits liberado permite tres clases especiales de números: (1) ± 0 se define como todos los bits de la mantisa y exponente siendo cero. (2) ± ∞ se define como todos los bits de la mantisa siendo cero, y todos los bits del exponente siendo uno. (3) Un grupo de números muy pequeños sin normalizar entre ± 1.2 × 10 -38 y ± 1.4 × 10 -45 . Estos son los más bajos números de precisión obtenida mediante la eliminación de la exigencia de que el dígito líder en la mantisa sea uno. Además de estas tres clases especiales, hay patrones de bits a los que no se les asigna un significado, comúnmente conocidos como NANs (No Un Número).

El estándar IEEE para doble precisión simplemente añade más bits a la mantisa y al exponente. De los 64 bits usados para almacenar un número de doble precisión, los bits de 0 a 51 son la mantisa, y los 52 a 62 bits son el exponente, y el bit 63 es el de signo. Al igual que antes, la mantisa es de entre uno y menos de dos, es decir, M = 1+ m51 2-1+ m50 2-2+ m49 2-3… Los 11 bits del exponente forman un número entre 0 y 2047, con una compensación de 1023, permitiendo exponentes del 2-1023 a 21024. El más grande y el más pequeño de los números permitidos son ± 1.8 × 10308 × ± 2.2 × 10-308, respectivamente. ¡Estos son números increíblemente grandes y pequeños! Es muy raro encontrar una aplicación donde la precisión simple no sea adecuada. Probablemente nunca encuentres un caso en que la doble precisión límite lo que quieres lograr.


Texto basado en: "The Scientist and Engineer's Guide to Digital Signal Processing"
Steve Smith