Desarrollo de aplicaciones de audio en C++: un enfoque
Transcripción
Desarrollo de aplicaciones de audio en C++: un enfoque
UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA Proyecto de Fin de Carrera de Ingeniero Informático Desarrollo de aplicaciones de audio en C++: un enfoque práctico Carlos Jiménez de Parga Bernal - Quirós Dirigido por: D. Antonio Jiménez de Parga Bernal - Quirós Supervisado por: D. José Luis Fernández Marrón Curso: 2010 - 2011 (21 de diciembre de 2010) Desarrollo de aplicaciones de audio en C++: un enfoque práctico Proyecto de Fin de Carrera de modalidad específica Realizado por: Carlos Jiménez de Parga Bernal - Quirós Dirigido por: D. Antonio Jiménez de Parga Bernal - Quirós Supervisado por: D. José Luis Fernández Marrón Tribunal calificador: a Presidente: D./D . ....................................................................................................................... a Secretario: D./D . ........................................................................................................................ a Vocal: D./D . ............................................................................................................................... Fecha de lectura y defensa: ........................................................................................................ Calificación................................................................................................................................ 1 1. Resumen: Estudio sobre el panorama actual del desarrollo de aplicaciones de audio de alto rendimiento en entornos multiplataforma escritos en lenguaje C++. Abordado desde la perspectiva teórica de los fundamentos físicos del sonido, la tecnología electrónica, los sintetizadores musicales, software libre para el desarrollo de aplicaciones y enfoque teórico y práctico sobre los principios del tratamiento digital de señales. 2. Lista de palabras clave: Onda, Sonido, Fourier, Armónicos, Espectro, Instrumentos musicales, Sintetizador, Teorema de Nyquist, Aliasing, Decibelios, Síntesis musical, DirectX, DirectSound, COM, OpenAL, Buffer, MIDI, Sistema Exclusivo, Canal MIDI, Esquemático MIDI, DirectMusic, DirectMIDI, Formatos de sonido, Audiere, PCM, WAV, AIFF, MP3, XM, MOD, Procesador Digital de Señal, FFT, Transormada Rápida de Fourier, Filtros, FIR, IIR, Envolventes. 3. Traducción del título: Audio application development in C++: a practical approach 4. Traducción del resumen: Study on currently available multi-platform libraries in C++ for high-performance audio application development. Theoretical approach to the physics of sound, audio synthesizer hardware, open-source software for audio applications and signal processing principles. 5. Traducción de las palabras clave: Waveform, Sound, Fourier, Armonics, Spectrum, Musical instruments, Synthesizer, Nyquist’s theorem, Aliasing, Decibels, Music synthesis, DirectX, DirectSound, OpenAL, Buffer, MIDI, Exclusive System, MIDI Channel, MIDI schematic, DirectMusic, DirectMIDI, Sound formats, Audiere, PCM, WAV, AIFF, MP3, XM, MOD, Digital Signal Processor, FFT, Fast Fourier Transform, Filters, FIR, IIR, Envelopes. 2 6. Índice 3 1. Resumen........................................................................................................................... 2 2. Lista de palabras clave..................................................................................................... 2 3. Traducción del título......................................................................................................... 2 4. Traducción del resumen................................................................................................... 2 5. Traducción de las palabras clave..................................................................................... 2 6. Índice................................................................................................................................ 3 7. Listas de figuras............................................................................................................... 4 8. Cuerpo.............................................................................................................................. 5 8.1 Capítulo 1................................................................................................................... 5 8.1.1 Contexto........................................................................................................ 5 8.1.2 Trabajos anteriores........................................................................................ 9 8.1.3 Aportaciones y conclusiones......................................................................... 9 8.2 Capítulo 2................................................................................................................... 10 8.2.1 Contexto........................................................................................................ 10 8.2.2 Validación mediante un prototipo.................................................................. 11 8.2.3 Trabajos anteriores........................................................................................ 12 8.2.4 Aportaciones y conclusiones......................................................................... 12 8.3 Capítulo 3................................................................................................................... 12 8.3.1 Contexto........................................................................................................ 12 8.3.2 Validación mediante un prototipo.................................................................. 13 8.3.3 Trabajos anteriores........................................................................................ 13 8.3.4 Aportaciones y conclusiones......................................................................... 13 8.4 Capítulo 4................................................................................................................... 14 8.4.1 Contexto......................................................................................................... 14 8.4.2 Trabajos anteriores........................................................................................ 15 8.4.3 Aportaciones y conclusiones......................................................................... 15 8.5 Capítulo 5................................................................................................................... 16 8.5.1 Contexto......................................................................................................... 16 8.5.2 Desarrollo del subproyecto DirectMIDI.......................................................... 17 8.5.3 Trabajos anteriores........................................................................................ 18 8.5.4 Aportaciones y conclusiones......................................................................... 18 8.6 Capítulo 6................................................................................................................... 18 8.6.1 Contexto........................................................................................................ 18 8.6.2 Validación mediante un prototipo.................................................................. 19 8.6.3 Aportaciones y conclusiones......................................................................... 20 8.7 Capítulo 7................................................................................................................... 20 8.7.1 Contexto........................................................................................................ 20 8.7.2 Validación mediante un prototipo.................................................................. 22 8.7.3 Trabajos anteriores........................................................................................ 22 8.7.4 Aportaciones y conclusiones......................................................................... 22 9. Listado de referencias y Bibliografía................................................................................. 23 10. Listado de siglas, abreviaturas y acrónimos..................................................................... 28 11. Anexos............................................................................................................................. 29 7. Listas de figuras Figura 8.1 – Umbrales de tolerancia al sonido...................................................................... 6 Figura 8.2 – Formas de onda básicas................................................................................... 7 Figura 8.3 – Envolvente de volumen..................................................................................... 7 Figura 8.4 – Muestreo de señal............................................................................................. 8 Figura 8.5 – Arquitectura básica de DirectX.......................................................................... 10 Figura 8.6 – Ciclo de vida en cascada.................................................................................. 11 Figura 8.7 – Logotipo de MIDI............................................................................................... 14 Figura 8.8 – Logotipo del proyecto DirectMIDI...................................................................... 17 4 8. Cuerpo 8.1 Capítulo 1 8.1.1 Contexto En este capítulo se abordan varios conceptos de la teoría básica del sonido. Se comienza introduciendo al lector en los conceptos de generación de la onda y cómo ésta es transmitida por el espacio. Para explicar estos conceptos se exponen diversos ejemplos ilustrativos. Posteriormente, se aborda en el capítulo los tipos de onda que se dan en la naturaleza. Otro aspecto que se aborda en este capítulo son los conceptos de las magnitudes de la onda: periodo, longitud de onda, frecuencia y amplitud, aspectos éstos fundamentales en la física de la onda. Tan importante como los conceptos de magnitudes es la ecuación matemática que describe la onda, de la cual se realiza un desarrollo teórico. A continuación se explica uno de los conceptos fundamentales que serán de gran utilidad para poder seguir el tratado y que son de importancia relevante en el capítulo 7 de Tratamiento Digital de Señales. Estos son la representación de la señal en el dominio del tiempo y en el dominio de la frecuencia. Dentro de este apartado también se realiza una introducción al concepto de espectro. Se explica también en este capítulo el Teorema de Fourier que demuestra que cualquier función periódica continua, con un número finito de máximos y mínimos, puede desarrollarse en una serie trigonométrica uniformemente convergente, llamada serie de Fourier. Las conceptos de ondas armónicas y el ruido con sus respectivas representaciones en el dominio y en la frecuencia son un tema también tratado en este capítulo. Posteriormente se pasa a introducir la teoría de la propagación del sonido a través de diferentes medios físicos y sus características. Se describen las velocidades que alcanza el sonido en el agua, la madera y el acero. 5 En el tercer apartado del primer punto del capítulo se menciona, sin llegar a lo exhaustivo, el funcionamiento del oído humano, sus principales órganos componentes y cómo se relacionan estos con el cerebro para producir la sensación acústica. Por tanto, se explican cada una de estas partes que son: el oído externo, el oído medio y el oído interno. Como ejemplo ilustrativo se expone un diagrama de bloques que relaciona cada parte del oído con un sistema digital de audio. Dentro de este apartado se definen a grandes rasgos las principales cualidades del sonido que son: intensidad, tono, timbre y duración. Es conocido por todos el rango de frecuencias que abarca el espectro audible, esto es, un rango que va desde los 20 a los 20000 Hz. Como ejemplo práctico se representa mediante una tabla los diferentes rangos de frecuencia de las diferentes octavas musicales. Se incluye en este apartado un gráfico con los umbrales de la tolerancia humana de los sonidos en decibelios; como se muestra en la siguiente figura: Figura 8.1 Umbrales de tolerancia al sonido Por tanto, y para detallar más este apartado se ha querido añadir una tabla con los diferentes niveles de presión sonora. 6 Dentro de un nuevo apartado se explica los conceptos teóricos de la generación del sonido analógico y digital. Para explicar la idea de generación de sonido analógico se ha hecho alusión a los tres tipos básicos de instrumentos: de percusión, de viento y de cuerda. Ya dentro de este mismo apartado se explica la teoría de la síntesis analógica de audio y la electrónica que lo hace posible. Los componentes electrónicos fundamentales para la generación analógica son: Oscilador: es un circuito electrónico capaz de generar una onda periódica a una determinada frecuencia. Por ejemplo estos dos tipos de onda: Figura 8.2 Formas de onda básicas Generador de envolvente: es un circuito que genera una señal que permite controlar un parámetro dentro de un sistema de síntesis. La señal envolvente se suele usar para modular la amplitud de la señal armónica, y de esta manera simular las variaciones de amplitud de un instrumento de cuerda. Otros tipos de envolvente se utilizan para modular el tono o pitch de la señal. Figura 8.3 Envolvente de volumen Amplificador de ganancia controlada: es un sistema que utilizan los sintetizadores analógicos cuya ganancia puede ser controlada por una tensión auxiliar. 7 Filtro: es un circuito selectivo en frecuencia que deja pasar unas mientras atenúa otras. Este componente es fundamental para la síntesis analógica. Los tipos básicos son: paso-bajo, paso-alto, paso-banda y elimina-banda. Tan importante como la síntesis analógica es la síntesis digital, por eso se hace un repaso en este apartado a las técnicas que comenzaron a desarrollarse a finales del siglo XX para la generación digital de sonido. En este capítulo se explican: La síntesis aditiva: Consistente en generar un sonido periódico, más complejo, resultante de la suma de ondas sinusoidales más elementales de frecuencia múltiplo de una frecuencia base. Modulación de frecuencia o síntesis FM: Una de las formas de poder realizar síntesis de sonido es por medio de la modulación, que consiste en variar la frecuencia de la señal portadora en función de la señal moduladora, generando una señal modulada. Síntesis por tabla de onda: Consiste en reproducir pequeños fragmentos de sonidos digitalizados y almacenados en memoria escaládolos según la nota deseada. En el último apartado de este capítulo se abordan los conceptos de representación del sonido. Dentro del mismo se explica en detalle el concepto de Conversión analógico / digital, para lo cual es necesario entender el Teorema de Nyquist, que indica que para muestrear una señal es necesario hacerlo al menos al doble de la frecuencia máxima de la señal analógica de entrada. En el siguiente gráfico se muestra una señal analógica muestreada para su representación digital: Figura 8.4 Muestreo de señal 8 Dentro de este apartado se explica, igualmente, el proceso de conversión de una señal analógica en otra digital, de relevante importancia para el tratamiento digital de señales. Junto con el concepto de muestreo, se explican también los conceptos de cuantización y codificación. Antes de abordar el siguiente tema se hace un breve apunte sobre las formas de representación del sonido muestreado que son: PCM, PAM y PDM. No se podría terminar este apartado sobre la conversión analógico-digital sin explicar el concepto de aliasing, que es un efecto que puede ocurrir durante la conversión y que se produce cuando la señal que se muestra tiene componentes de frecuencia por encima de fs/2 o frecuencia de Nyquist. Por último, para concluir el capítulo se explica el concepto de conversión digital-analógica. 8.1.2 Trabajos anteriores Algunos trabajos anteriores que versan sobre los temas previamente expuestos son: John G. Proakis & Dimitris G. Manolakis – Tratamiento Digital de Señales, libros de Física de preuniversitario y Técnicas de Grabación modernas de la editorial Omega. 8.1.3 Aportaciones y conclusiones En este capítulo se ha pretendido dar a conocer al lector las nociones básicas de teoría del sonido, sin ánimo de ser demasiado teórico ni demasiado básico; tratando de abordar los conceptos complejos de una manera sencilla y entendible para el lector y/o estudiante. 9 8.2 Capítulo 2 8.2.1 Contexto Este capítulo se centra principalmente en la programación en DirectX con Visual C++ para la generación de audio con DirectSound. El capítulo comienza con una introducción a la filosofía de DirectX, desarrollada principalmente para los juegos en plataformas Windows que siempre han necesitado de altas prestaciones. Se explica conjuntamente la arquitectura de DirectX que es esencialmente una arquitectura software por capas, como se muestra en la figura 8.5: DirectX HAL HEL Hardware Figura 8.5 Arquitectura básica de DirectX Posteriormente se describen los componentes del paquete SDK de DirectX, para pasar finalmente a una breve introducción a la filosofía COM (Component Object Model – Modelo de Objetos Componentes), que junto a DCOM conforma uno de los pilares fundamentales de la arquitectura de componentes de Windows. Después de la breve introducción a COM se entra definitivamente, y ya en profundidad, en la programación en DirectSound. Se explica al lector los fundamentos de la arquitectura DirectSound para posteriormente desarrollar los distintos formatos de sonido soportados por la librería. Para explicar los fundamentos de programación en DirectSound se ha procedido a desglosar los contenidos en las siguientes partes: 10 Creación del objeto DirectSound Establecimiento del nivel cooperativo Creación del buffer primario Introducción a los buffers secundarios Pasos para crear los buffers secundarios Archivos de sonido Descripción del buffer Creación del buffer secundario Liberación de las interfaces COM Por último, y debido al interés por el sonido en tres dimensiones se explica cómo hacer que la aplicación soporte buffers en 3D. 8.2.2 Validación mediante un prototipo Para aportar un ejemplo de aplicación en DirectX / DirectSound, se ha desarrollado una aplicación en el entorno Visual C++ 2005, en modo consola, llamada dsound.exe que se podrá encontrar en el directorio de binarios del CDROM del proyecto. Para escribir esta aplicación Win32 se ha utilizado un esquema de ciclo de vida en cascada, puesto que los requisitos de los usuarios están bastante claros y la tecnología es madura: Figura 8.6 Ciclo de vida en cascada 11 8.2.3 Trabajos anteriores Existen otros trabajos anteriores sobre la programación de DirectSound, especialmente en la Web, pero fundamentalmente el libro de referencia para la programación en DirectX / DirectSound es el publicado por la editorial de Microsoft: a fondo DirectX. 8.2.4 Aportaciones y conclusiones Se ha procurado explicar los conceptos complejos de la arquitectura COM sin entrar en demasiado detalle, pues es un tema complicado para desarrolladores que están acostumbrados a ser clientes invocadores de tales objetos. Se ha pretendido, sobre todo, dar una visión de la potencia y el alcance mundial que DirectSound ha tenido durante su larga historia hasta el DirectX10. En las versiones actuales de DirectX aún se soportan las API’s de DirectSound con las antiguas interfaces; no obstante se ha creado una nueva arquitectura para sistemas de 64 bits y nuevas funciones de la API que han dejado un poco desplazadas a las antiguas de DirectSound. 8.3 Capítulo 3 8.3.1 Contexto En este capítulo se aborda la programación en la librería OpenAL. Esta librería es una versión multiplataforma de DirectSound de la compañía Creative Labs. Para iniciar el capítulo se introduce al lector en la arquitectura de OpenAL, mucho más sencilla y directa que la de su homólogo DirectX. Una vez explicado el funcionamiento de la arquitectura, se comienza a exponer al lector los fundamentos básicos para la programación en OpenAL. Para ello se ha dividido el apartado en las siguientes secciones a fin de seguir una metodología más pedagógica. Los apartados sobre la programación OpenAL son los siguientes: Inicialización 12 Trabajo con buffers Reproducción de sonido Posicionamiento 3D Salida de la aplicación Gestión de errores Por último se comentan las principales ventajas de OpenAL como librería multiplataforma de alto rendimiento y muy utilizada actualmente en videojuegos, junto con la ventaja adicional de que no es necesario la instalación de ningún plug-in para su ejecución. Además de esto, se comenta también que su programación y su diseño es mucho más versátil que el de DirectSound. 8.3.2 Validación mediante un prototipo Se ha desarrollado una aplicación con Visual C++ 2010 sobre Win32 y OpenAL, en modo consola, para demostrar las utilidades de esta API, así como ilustrar al lector con un ejemplo compuesto de varias fases de cómo escribir una aplicación con los elementos básicos de OpenAL. La aplicación de demostración se puede encontrar en el CD-ROM del proyecto en la carpeta de binarios, con el nombre openal.exe. Por último comentar que se ha utilizado un esquema de ciclo de vida en cascada como el que se ilustra en la figura 8.6. 8.3.3 Trabajos anteriores No hay constancia de otros trabajos en castellano sobre la librería de OpenAL. Para el desarrollo del proyecto se han utilizado los manuales oficiales que están publicados en la página principal de Creative Labs. 8.3.4 Aportaciones y conclusiones Se ha explicado al lector la programación en OpenAL de una forma metodológica y pedagógica que sea fácilmente comprensible, de esta forma, 13 se han evitado construir aplicaciones complejas con interfaces gráficas de usuario para no desviar la atención del lector hacia aspectos diferentes de la API de audio. 8.4 Capítulo 4 8.4.1 Contexto En este capítulo sobre tecnología MIDI se exponen todos los aspectos teóricos sobre la interfaz para la interconexión de instrumentos musicales digitales. En el principio del capítulo se presenta al lector en una breve historia de MIDI, así como el origen y la concepción de este sistema innovador que sigue perdurando en la actualidad. Figura 8.7 Logotipo de MIDI Posteriormente se explican algunos conceptos de la utilización de la tecnología y los entornos más usuales donde suele aplicarse. Como sección de interés para lectores especializados en diseño electrónico se explica con detalle todo el hardware en torno al sistema MIDI. Para ello se ilustran detalles sobre los conectores, cables MIDI, pines, y esquemáticos. Se exponen de esta forma las ideas básicas de los diferentes puertos MIDI: MIDI in, MIDI out y MIDI thru. Además de la configuración explicada anteriormente se comentan los diferentes modos de interconexión de sistemas, por medio de USB, FireWire y conexiones de red mLAN. No podía faltar en un capítulo sobre MIDI información sobre los diferentes mensajes que componen la especificación mantenida por la MMA. Por tanto se comenta de manera detallada este significado de los campos de los diferentes 14 mensajes midi. Para ello se explican los fundamentos de los canales MIDI y los mensajes de canal: note on, note off, poly aftertouch, channel aftertouch, etc. Además de los mensajes de canal se explican los mensajes de sistema exclusivo, necesarios para controlar los dispositivos y que permiten el uso de un lenguaje ajeno al protocolo musical, y que al ser propietario de cada sistema se requiere saber su especificación según cada fabricante. Para entender esta terminología se explican en el capítulo los fundamentos de transmisión y recepción SYX. Al finalizar el capítulo se explican los fundamentos del tiempo MIDI y su sincronización. 8.4.2 Trabajos anteriores Hay mucha bibliografía y referencias en la Web tanto en inglés como en castellano, debido a la repercusión y popularidad del tema. Principalmente, se ha recurrido a fuentes en Internet, especialmente a la Web de la MMA y una página Web bastante afamada entre los desarrolladores llamada peculiarmente: MIDI Technical Fanatic's Brainwashing Center 8.4.3 Aportaciones y conclusiones Se ha pretendido en este capítulo introducir al lector en los fundamentos y la terminología MIDI. Se ha intentado ser breve en los conceptos menos relevantes y más exhaustivo en los conceptos orientados a la programación de sistemas en esta tecnología. El enfoque principalmente teórico de este capítulo ha servido como puente para introducir al lector en el siguiente capítulo que versará sobre programación MIDI con la librería LGPL DirectMIDI. 15 8.5 Capítulo 5 8.5.1 Contexto Una vez explicados los conceptos básicos sobre MIDI se introduce al lector en los fundamentos de programación de estos sistemas. Para entender los entresijos técnicos de la programación en MIDI se ha centrado el capítulo en la librería de software libre DirectMIDI. Esta librería está basada en DirectX y actualmente se encuentra disponible para sistemas Windows de 32 bits. Este 1 software utiliza una licencia GNU LGPL que, según Richard Stallman , es un método para licenciar software de tal forma que su uso y modificación permanezcan siempre libres y queden en la comunidad, permitiendo su uso libremente por otros desarrolladores sin coste alguno. Para comenzar la explicación sobre el uso de la librería se introduce primeramente al lector en los fundamentos de las interfaces COM más importantes de DirectMusic que permitirán la implementación de las funcionalidades. Después se explica cómo obtener el software del repositorio de software libre 2 SourceForge . Posteriormente se ilustra mediante gráficos la arquitectura de objetos que constituyen la librería y sus objetivos dentro del sistema de clases. Para mostrar el uso de la librería en C++ se han seguido los siguientes pasos en la metodología: Configuración del entorno de desarrollo Creación de las primeras líneas de código Preparación de la captura de música Inicialización de objetos Comienzo de la captura de música Aumento del límite de instrumentos 1 es un programador estadounidense y figura relevante del movimiento por el software libre en el mundo. Ver proyecto GNU. 2 http://www.sourceforge.net 16 o DLS de alto nivel o DLS de bajo nivel Terminación la aplicación Por último se explica cómo manejar excepciones para el tratamiento de errores de la librería. 8.5.2 Desarrollo del subproyecto DirectMIDI 3 El proyecto DirectMIDI se comenzó en septiembre de 2002 con la idea de incluirlo en un futuro proyecto de fin de carrera universitario. Inicialmente se orientó a su publicación en la revista electrónica para programadores en 4 plataformas Windows CodeProject en la que obtuvo una gran aceptación y valoración. Posteriormente se licenció como software libre y se publicó en el repositorio SourceForge. El proyecto ha sido desarrollado en Visual C++ .NET 7.1 utilizando la API Win32 y DirectX. Para su desarrollo se ha utilizado un esquema de ciclo de vida en espiral, puesto que es un proyecto complejo, novedoso y los cambios en los requisitos son frecuentes. Se puede encontrar una copia de la librería en el directorio de fuentes del capítulo y un ejemplo de programación en el directorio de binarios, llamado Example_mid.exe. Para ejecutar este programa es necesario tener un ordenador con un puerto de entrada MIDI. Figura 8.8 Logotipo del proyecto DirectMIDI 3 4 Página Web del proyecto: http://directmidi.sourceforge.net http://www.codeproject.com 17 8.5.3 Trabajos anteriores Existe un libro sobre programación MIDI en Windows que ha sido un referente en toda la década llamado: Maximum MIDI music applications in C++. 8.5.4 Aportaciones y conclusiones Las aportaciones de este capítulo tienen una doble vertiente: por un lado se ha intentado explicar lo más cómodamente posible la programación con la librería de manera que sea amigable y entretenida para el programador de aplicaciones musicales; por otro lado, se ha de tener en cuenta que el desarrollo de la librería DirectMIDI, que transcurrió entre los años 2002 a 2004, fue orientado a obtener una librería MIDI con resultados eficientes y que pudiera ser utilizada en el campo profesional y de investigación. Una de las características que se resaltaron de la librería fue la baja latencia del timer MIDI que usa DierectMusic y que permite aplicaciones con un mejor rendimiento. Actualmente la librería se encuentra en estado beta y disponible desde DirectX 9. Se pretender publicar en breve una versión para plataformas de 64 bits. 8.6 Capítulo 6 8.6.1 Contexto En este capítulo se aborda el tema fundamental para el programador de aplicaciones de audio, que es el problema de añadir capacidades de reproducción y manipulación de ficheros de audio conocidos. Para comenzar, se introduce una sección sobre el estado actual de las librerías de reproducción de audio. Posteriormente se presenta al lector la librería de 5 software libre Audiere , un potente software en varias plataformas que permite la reproducción de diversos formatos de audio: Ogg Vorbis, MP3, WAV, MOD, XM. 5 http://audiere.sourceforge.net/ 18 La librería, que está implementada y orientada a la programación en C++, accede al hardware de audio de varias plataformas con diferentes tecnologías: DirectSound y WinMM en Windows y OSS en Linux. Para iniciar al lector en la programación en Audiere se comienza explicando los fundamentos de la API, así como la jerarquía de clases que componen el sistema. Por tanto para llegar a reproducir un fichero de audio son necesarios los siguientes pasos que se explican en el capítulo: La clase AudioDevice La clase OutputStream Reproducción y efectos Por último se explica cómo acceder al buffer PCM para obtener las muestras. En este caso se ilustra un ejemplo que invierte un fichero de música MP3. Para finalizar el capítulo se describen las características de los principales formatos de audio. Los formatos que se explican en este capítulo son: WAV AIFF MPEG:MP3 General Midi MID XM / MOD 8.6.2 Validación mediante un prototipo En este capítulo se han desarrollado dos prototipos de prueba para ilustrar los ejemplos explicados. El primero de ellos llamado audiere.exe demuestra las capacidades de la librería usando las funciones play, setPan (para cambiar el balance), SetPitchShift (para cambiar la frecuencia de reproducción de las muestras) y stop. La segunda aplicación desarrollada se llama invert.exe cuya finalidad es leer un fichero de audio y reescribir las muestras al revés (en backward). 19 Para desarrollar estos proyectos se ha seguido un ciclo de vida en cascada puesto que la tecnología es madura y los requisitos son conocidos desde el principio. 8.6.3 Aportaciones y conclusiones La idea fundamental de este capítulo es iniciar al programador de aplicaciones de audio en el uso de librerías de código fuente abierto y software libre para su inclusión en sus programas. Con la librería que se expone en el capítulo se pretende facilitar en gran medida el desarrollo de aplicaciones para reproducir archivos de audio que de lo contrario conllevarían un período de desarrollo muy largo y un aumento del coste. Otro aspecto que se ha intentado exponer son los diferentes formatos de audio existentes en la actualidad y que están más extendidos en el mundo del desarrollo de software de audio digital. 8.7 Capítulo 7 8.7.1 Contexto El capítulo final del tratado se orienta a los fundamentos del Tratamiento Digital de Señal. Este tema es transcendente por su amplia difusión en el entorno académico y profesional y porque su repercusión en el desarrollo de aplicaciones tanto de imagen como de audio ha sido muy importante en estos últimos años. El capítulo comienza explicando los aspectos más relevantes de la historia del tratamiento digital de señales y sus ámbitos de aplicación. Posteriormente, se comienza a introducir al lector en los algoritmos básicos utilizados en el audio que se clasifican en lineales y no lineales. A continuación se pasa a explicar los fundamentos de los dos tipos de filtros existentes: los filtros IIR y FIR, que son los acrónimos de Infinite Impulse Response y Finite Impulse Response respectivamente. Para explicar estos conceptos se detallan con gráficos sus 20 principales estructuras y se presentan sus ecuaciones. Dentro del apartado de algoritmos se explica también en qué consiste la técnica de envolvente de volumen, así como el retardo y los efectos basados en el mismo: coros, eco, reverberación, phaser y flanger. Posteriormente, y dentro del apartado de algoritmos, se explican los fundamentos de la técnica de Compresión del rango dinámico. Una de las herramientas más utilizadas actualmente en los proyectos de audio es la Transformada Rápida de Fourier o FFT, la cual se explica detalladamente a lo largo del capítulo. Para finalizar, se introduce al lector en la programación C++ de aplicaciones de TDS utilizando la librería gratuita para fines educacionales Mitov. En concreto se explica detalladamente al lector cómo se desarrolla una aplicación en Visual C++ con esta librería desde cero. Para conseguir este propósito se utilizará la librería GUI MFC (Microsoft Foundation Classes), que tiene la ventaja de ser una API primitiva y no necesitar ninguna versión de sistema operativo Windows avanzado, ni tan siquiera la instalación de .NET. En primer lugar se explica al lector cómo utilizar las librerías SignalLab y AudioLab para generar una forma de onda básica por pantalla y poder reproducir un fichero WAV. Después de explicar estos conceptos se presenta un ejemplo de aplicación real de uso de la librería para el análisis de señales de audio. Para conseguir este propósito se explica al lector el diagrama de bloques que usará la aplicación, así como el código fuente necesario para instanciar los objetos Mitov que requiere la aplicación. El ejemplo que se propone es una aplicación C++ para el análisis de una forma de onda en formato WAV, aplicación de filtros paso banda, paso bajo y paso alto y ecualización de 10 bandas. Por último la salida de la señal se visualizará en tres tipos de presentación diferentes: espectrograma (waterfall), dominio del tiempo y dominio de la frecuencia (FFT). 21 8.7.2 Validación mediante un prototipo Como se ha mencionado en el apartado anterior, se ha desarrollado una aplicación en Visual C++ que refleja la tendencia actual del uso de las técnicas fundamentales del Tratamiento Digital de Señal. Para desarrollar la aplicación propuesta se ha servido de la librería gratuita para fines no comerciales Mitov. Para la ingeniería del proyecto se ha utilizado un ciclo de vida en cascada (figura 8.6), puesto que los requisitos fueron indicados por el Director y eran claros desde el principio. La aplicación se puede encontrar en el CD-ROM del proyecto dentro del directorio de binarios con el nombre tds.exe. 8.7.3 Trabajos anteriores Se han escrito muchos tratados sobre el Tratamiento Digital de Señal, aunque la mayor parte de la bibliografía se encuentra en lengua inglesa. En la Web hay multitud de referencias sobre este tema tanto desde el punto de vista comercial, académico como de aficionado. No obstante, el principal libro de referencia anteriormente escrito y utilizado por la mayoría de la Universidades es el de John G. Proakis & Dimitris G. Manolakis – Tratamiento Digital de Señales. 8.7.4 Aportaciones y conclusiones La idea de este capítulo es introducir al lector en los conceptos de Tratamiento de Señales, con el ánimo de hacer una lectura amena de un tema eminentemente complejo. Aunque este capítulo es de dificultosa lectura se ha intentado centrase en el aspecto más pragmático del mismo, huyendo de un riguroso formalismo matemático y centrándose en los aspectos que conciernen a las aplicaciones informáticas de sonido en la actualidad. 22 9. Listado de referencias y Bibliografía 9.1 Capítulo 1 Libros 1. [Alonso et al] Diseño y desarrollo Multimedia, Sistemas, Imagen, Sonido y Vídeo, Ra-ma, 2002. 2. [Candel et al] Física COU, Anaya, 1990. 3. [Cuenca et al] Tecnología Básica del Sonido I, Paraninfo, 2006. 4. [Miles et al] Técnicas de Grabación modernas, Omega, 2007. 5. [Sánchez] Apuntes Redes 3º IT Informática de Sistemas, 1997. La Web 6. http://www.mrfizzix.com/instruments/ 7. http://www.ehu.es/acustica/espanol/fisiologia1/siaues/siaues.html 8. [Herrera] http://sam.atlantes.org/ 9. [Wikipedia] http://es.wikipedia.org/wiki/Cualidades_del_sonido 10. [Wikipedia] http://es.wikipedia.org/wiki/O%C3%ADdo 11. [Wikipedia] http://es.wikipedia.org/wiki/Octava 23 12. [Wikipedia] http://en.wikipedia.org/wiki/Frequency_modulation_synthesis 13. [Wikipedia] http://es.wikipedia.org/wiki/S%C3%ADntesis_mediante_tabla_de_ondas 9.2 Capítulo 2 Libros 14. [Bradley et al] A fondo DirectX, Microsoft Press, 1998. 15. [Colouris et al] Sistemas Distribuidos: conceptos y diseño, Pearson, 2001. 16. [Gordon] Programación COM y COM+ , Anaya Multimedia, 2000. La Web 17. [Gimeno] http://usuarios.multimania.es/andromeda_studios/paginas/tutoriales/articulo02. htm 18. [MSDN] http://msdn2.microsoft.com/es-es/default.aspx 9.3 Capítulo 3 Manuales 19. [Creative Labs] OpenAL Programmer's Guide - OpenAL Versions 1.0 and 1.1 24 La Web 20. [Wikipedia] http://en.wikipedia.org/wiki/OpenAL 9.4 Capítulo 4 Libros 21. [Alonso et al] Diseño y desarrollo Multimedia, Sistemas, Imagen, Sonido y Vídeo, Ra-ma, 2002. 22. [Miles et al] Técnicas de Grabación modernas, Omega, 2007. La Web 23. [MIDI Manufacturers Association] http://www.midi.org/ 24. http://home.roadrunner.com/~jgglatt/ 25. [Wikipedia] http://es.wikipedia.org/wiki/MIDI 9.5 Capítulo 5 La Web 26. [Jiménez de Parga] http://directmidi.sourceforge.net/ 27. [MSDN] http://msdn2.microsoft.com/es-es/default.aspx 25 9.6 Capítulo 6 Libros 28. [Alonso et al] Diseño y desarrollo Multimedia, Sistemas, Imagen, Sonido y Vídeo, Ra-ma, 2002. La Web 29. [Arribas] http://www.lpi.tel.uva.es/~nacho/docencia/ing_ond_1/trabajos_01_02/formatos_audi o_digital/html/midiformat.htm 30. [Austin et al] http://audiere.sourceforge.net/documentation.php 31. [MSDN] http://msdn2.microsoft.com/es-es/default.aspx 32. [Wikipedia] http://es.wikipedia.org/wiki/Waveform_Audio_Format 33. [Wikipedia] http://es.wikipedia.org/wiki/AIFF 34. [Wikipedia] http://es.wikipedia.org/wiki/MP3 35. [Wikipedia] http://es.wikipedia.org/wiki/General_MIDI 36. [Wikipedia] http://en.wikipedia.org/wiki/MOD_(file_format) 37. [Wikipedia] http://en.wikipedia.org/wiki/XM_(file_format) 26 9.7 Capítulo 7 Libros 38. [Schildt] Programación con MFC 6.0, Osborne McGraw-Hill, 1999. La Web 39. [MSDN] http://msdn2.microsoft.com/es-es/default.aspx 40. [Mitov documentation] http://www.mitov.com 41. [Wikipedia] http://en.wikipedia.org/wiki/Digital_audio_editor 42. [Wikipedia] http://en.wikipedia.org/wiki/Sound_effect 43. [Wikipedia] http://paws.kettering.edu/~drussell/demos.html 44. [Wikipedia] http://en.wikipedia.org/wiki/Cooley–Tukey_FFT_algorithm 45. [Wikipedia] http://en.wikipedia.org/wiki/Butterfly_diagram 27 10. Listado de siglas, abreviaturas y acrónimos Hz: Hertzios VCA: Voltaje Controlled Amplifier (Amplificador controlador por tensión) FM: Frequency Modulation (Modulación de frecuencia) FFT: Fast Fourier Transform IIR: Infinite Impulse Response (Respuesta infinita al impulso) FIR: Finite Impulse Response (Respuesta finita al impulso) PCM: Pulse Code Modulation (Modulación por pulsos codificados) PAM: Pulse Amplitude Modulation (Modulación por amplitud de pulsos) PDM: Pulse Density Modulation (Modulación de señal por densidad de impulsos) SDK: Software Development Kit COM: Component Object Model (Modelo de objetos componentes) DCOM: Modelo de objetos componentes distribuidos CD-ROM: Compact Disk Read-Only Memory API: Application Programming Interface (Interfaz de programación de aplicaciones) MIDI: Musical Instrument Digital Interface (Interfaz digital de instrumentos musicales) mLAN: Music Local Area Network LGPL: Lesser General Public License GNU: GNU no es Unix DLS: Downloadable Sounds MP3: Mpeg I audio layer III WAV: Waveform audio file format OSS: Open Sound System AIFF: Audio Interchange File Format MID: General MIDI format MOD: Amiga music Module file 28 XM: Extended Module GUI: Graphical User Interface 11. Anexos A continuación se adjunta el tratado teórico correspondiente al proyecto de fin de carrera. Esta obra está bajo una licencia Reconocimiento 3.0 España de Creative Commons. Para ver una copia de esta licencia, visite http://creativecommons.org/licenses/by-sa/3.0/es/ o envie una carta a Creative Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. 29 Desarrollo de Aplicaciones de Audio en C++ Un enfoque práctico Carlos Jiménez de Parga Bernal - Quirós A mis padres Antonio y Carolina, por comprenderme y acompañarme estos 12 años que he dedicado al estudio de esta ciencia con tan gran potencial creativo como es la Informática. En recuerdo de las personas que nos dejaron en el camino, especialmente a mi tío Pepe y mi abuelo Juan, el cual me enseñó a apreciar la música. Prefacio Es evidente el auge en capacidad de cómputo de los sistemas con microprocesador en la actualidad. En un breve período de tiempo, hemos pasado de procesadores de 8 bits con una capacidad computacional baja, a procesadores de 64 bits con un alta frecuencia de reloj y gran capacidad de cálculo. Así mismo, y paralelamente a este desarrollo en microprocesadores para ordenadores personales, ha surgido una industria de fabricación de dispositivos externos que permiten realizar tareas de E/S impensables hace veinte años, en especial, los dispositivos de procesamiento de sonido como son: tarjetas de audio, DSP’s (procesadores digitales de señal) y módulos hardware de síntesis de sonido, basados en los últimos avances de la electrónica de física acústica, software y multiprocesadores. El sonido digital se ha convertido en objeto de estudio y forma parte de nuestra cultura de hoy en día. Como ejemplos caben destacar: la música, donde ya no es difícil escuchar temas o composiciones donde se inserte un sonido digital, los videojuegos, donde las últimas tecnologías se abren paso para producir efectos más realistas que introducen al jugador en una atmósfera de gran verosimilitud, el cine, con efectos acústicos en tres dimensiones que realzan las escenas, y un amplio rango de investigaciones que van desde la compresión de audio y reconocimiento de voz, hasta la Inteligencia Artificial. En lo que respecta a la programación de sonido profesional en computadores personales, especialmente en Windows, Linux y Mac OS, se han desarrollado componentes software accesibles a través del API del sistema operativo, que permiten acceder a funciones de bajo nivel de manejo de puertos MIDI y realizar E/S de formas de onda PCM a través de los canales de audio de las tarjetas; abstrayendo la complejidad de la programación del hardware. Estas API’s son esenciales para comprender el modelo de programación de aplicaciones de audio profesionales, donde la latencia debe ser lo más baja posible y poder conseguir así las máximas garantías de rendimiento y fiabilidad. Tan importante como conocer el software de abstracción de las funcionalidades básicas de una tarjeta, es la teoría elemental del sonido, como son: su comportamiento físico, sus parámetros, conversión A/D y D/A, la percepción humana de las vibraciones acústicas en el espacio y los teoremas fundamentales de física. Es lógico que en un mundo donde la señal de sonido es computable y analizable, sea posible realizar un tratamiento digital de señal para poder manipularla, filtrarla, mezclarla y conseguir efectos en tiempo real sobre las muestras capturadas por algún dispositivo externo. Esto, por lo tanto, es un tema trascendental a nivel actual y profesional. iii Índice Dedicatoria.................................................................................................................... Prefacio......................................................................................................................... Índice............................................................................................................................ i iii v PARTE I: Conceptos preliminares........................................................................... 1. Teoría del sonido.......................................................................................... 1.1 Física de la onda................................................................................... 1.1.1 El concepto de onda.................................................................. 1.1.2 Magnitudes de la onda.............................................................. 1.1.3 Ecuación matemática de la onda............................................... 1.2 El sonido como forma de onda............................................................. 1.2.1 Dominio del tiempo y frecuencia.............................................. 1.2.2 Teorema de Fourier................................................................... 1.2.3 Ondas armónicas y ruido........................................................... 1.2.4 Propagación del sonido............................................................. 1.3 Percepción humana del sonido............................................................. 1.3.1 Funcionamiento del oído........................................................... 1.3.2 Cualidades del sonido............................................................... 1.3.3 Espectro audible e intensidad sonora (Decibelios)................... 1.4 Generación de sonido analógico y digital............................................. 1.4.1 Instrumentos musicales convencionales................................... 1.4.2 Síntesis analógica...................................................................... 1.4.3 Técnicas de síntesis digital........................................................ 1.5 Representación del sonido.................................................................... 1.5.1 Conversión Analógico / Digital................................................... 1.5.1.1 Teorema de Nyquist................................................... 1.5.1.2 Proceso de conversión................................................ 1.5.1.3 El problema del aliasing............................................. 1.5.2 Conversión Digital / Analógico................................................... Bibliografía y referencias........................................................................... 1 3 3 3 4 6 8 8 10 12 13 15 15 17 18 20 20 23 29 34 34 34 34 37 38 40 PARTE II: Programación.......................................................................................... 2. DirectX y la API DirectSound...................................................................... 2.1 ¿Qué es DirectX?.................................................................................. 2.1.1 Introducción.............................................................................. 2.1.2 Filosofía de DirectX.................................................................. 2.1.3 Arquitectura de DirectX............................................................ 2.1.4 Componentes de DirectX.......................................................... 2.1.5 Programación en DirectX.......................................................... 2.1.6 Tecnología COM....................................................................... 2.2 Programación en DirectSound.............................................................. 2.2.1 Introducción.............................................................................. 2.2.2 Arquitectura de DirectSound..................................................... 2.2.3 Formato de sonido soportado por DirectSound........................ 2.2.4 Creación del objeto DirectSound.............................................. 43 45 45 45 46 46 47 47 48 52 52 52 53 54 v 2.2.5 Establecer el nivel cooperativo................................................. 2.2.6 Crear el buffer primario............................................................ 2.2.7 Introducción a los buffers secundarios...................................... 2.2.8 Pasos para crear los buffers secundarios.................................. 2.2.9 Archivos de sonido................................................................... 2.2.10 Describir el buffer.................................................................... 2.2.11 Creación del buffer secundario................................................ 2.2.12 Sonido en tres dimensiones...................................................... 2.2.13 Bloqueo del buffer.................................................................... 2.2.14 Reproducir y posicionar el buffer en 3D.................................. 2.2.15 Liberar las interfaces................................................................ Bibliografía y referencias................................................................................. 55 56 57 57 58 58 59 60 61 63 64 65 3. OpenAL........................................................................................................... 3.1 Introducción a OpenAL........................................................................ 3.2 Arquitectura de OpenAL...................................................................... 3.3 Programación con OpenAL.................................................................. 3.3.1 Inicialización............................................................................. 3.3.2 Trabajar con buffers.................................................................. 3.3.3 Reproducir sonido..................................................................... 3.3.4 Posicionamiento 3D.................................................................. 3.3.5 Salida de la aplicación............................................................... 3.3.6 Gestión de errores..................................................................... 3.4 Ventajas de OpenAL............................................................................ Bibliografía y referencias................................................................................. 67 67 67 69 69 71 73 74 76 77 77 78 PARTE III: MIDI....................................................................................................... 4. Introducción al MIDI.................................................................................. 4.1 ¿Qué es MIDI?...................................................................................... 4.1.1 Un poco de historia................................................................... 4.1.2 Conceptos básicos..................................................................... 4.1.3 Electrónica MIDI...................................................................... 4.1.3.1 Interconexión de sistemas........................................... 4.1.3.2 Esquemáticos............................................................... 4.2 Especificación MIDI............................................................................. 4.2.1 Introducción.............................................................................. 4.2.2 Canales MIDI............................................................................ 4.3 Mensajes MIDI..................................................................................... 4.3.1 Mensajes de canal..................................................................... 4.3.2 Mensajes de sistema.................................................................. 4.3.3 Mensajes de sistema exclusivo.................................................. 4.3.4 Mensajes de modo.................................................................... 4.3.5 Mensajes de tiempo real........................................................... 4.4 Sincronización y tiempo MIDI............................................................. Bibliografía y referencias........................................................................... 79 81 81 81 82 84 84 87 88 88 88 90 90 93 94 95 96 98 99 5. Programación MIDI.................................................................................... 5.1 Introducción.......................................................................................... 5.2 La API DirectMusic de DirectX........................................................... 5.2.1 Características principales........................................................ 101 101 102 102 vi 5.2.2 Principales interfaces COM...................................................... 5.3 Desarrollando aplicaciones con la librería DirectMIDI........................ 5.3.1 Introducción.............................................................................. 5.3.2 Esquema de la arquitectura DirectMIDI................................... 5.3.3 Comenzando la aplicación........................................................ 5.3.3.1 Primer paso: configurar el entorno de desarrollo........ 5.3.3.2 Segundo paso: las primeras líneas de código.............. 5.3.3.3 Tercer paso: preparando la captura de música............ 5.3.3.4 Cuarto paso: inicializando objetos.............................. 5.3.3.5 Quinto paso: comenzar la captura de música.............. 5.3.3.6 Sexto paso: aumentando el límite de instrumentos..... 5.3.3.7 Séptimo paso: terminar la aplicación.......................... 5.3.4 Manejo de excepciones............................................................. Bibliografía y referencias........................................................................... 103 104 104 106 107 107 107 108 109 111 112 118 119 120 PARTE IV: Reproducción de formatos de sonido.................................................. 6. Reproducción de formatos de sonido.......................................................... 6.1 Introducción.......................................................................................... 6.2 La librería Audiere................................................................................ 6.2.1 Introducción.............................................................................. 6.2.2 Clase AudioDevice................................................................... 6.2.3 Clase OutputStream.................................................................. 6.2.4 Reproducción y efectos............................................................. 6.2.5 Acceso a los datos PCM de un buffer....................................... 6.3 Formatos de sonido conocidos.............................................................. 6.3.1 Formato WAV.......................................................................... 6.3.2 Formato AIFF........................................................................... 6.3.3 Formatos MPEG: MP3............................................................. 6.3.4 Formato General MIDI............................................................. 6.3.5 Formato MOD / XM................................................................. Bibliografía y referencias.......................................................................... 121 123 123 124 124 126 127 128 129 133 133 133 134 136 141 143 PARTE V: Tratamiento Digital de Señal................................................................. 7. Tratamiento Digital de Señal...................................................................... 7.1 Procesado digital de señal aplicado al audio........................................ 7.2 Algoritmos básicos para audio.............................................................. 7.2.1 Filtros........................................................................................ 7.2.2 Envolvente de volumen............................................................ 7.2.3 Retardo...................................................................................... 7.2.4 Compresión............................................................................... 7.3 Transformada rápida de Fourier (FFT)................................................. 7.4 Librerías C++........................................................................................ 7.4.1 Modelos de librerías.................................................................. 7.4.2 Mitov......................................................................................... 7.4.3 Principales librerías de Mitov................................................... 7.4.4 Conceptos básicos de las librerías SignalLab y AudioLab....... 7.5 Desarrollo de una aplicación de procesado de señal............................. 7.5.1 Diagrama de bloques................................................................ 7.5.2 Declaración de objetos de librería............................................ 7.5.3 Inicialización e interconexión de objetos.................................. 145 147 147 148 148 150 150 152 153 158 158 158 158 159 163 163 164 165 vii 7.5.4 Comportamiento de los filtros y el ecualizador........................ 167 7.5.5 Aspecto final de la aplicación................................................... 170 Bibliografía y referencias............................................................................................. 171 APÉNDICE................................................................................................................. A1. Mensajes de la especificación MIDI 1.0................................................. A2. Lista de mensajes de controladores MIDI 1.0....................................... A3. Lista de instrumentos GM1 (General MIDI 1.0).................................. A4. Tabla de frecuencias de notas musicales en Hertzios........................... viii 173 173 176 179 181 Parte I Conceptos preliminares C A P Í T U L O 1 • Teoría del sonido 1 Teoría del sonido 1.1 Física de la onda En nuestro entorno físico habitual estamos expuestos a perturbaciones ondulatorias transmitidas por distintos medios: líquidos, sólidos, gases o incluso el vacío. La naturaleza de la perturbación es diferente según el medio, y también la manera de percibirla. Un ejemplo cotidiano es el de la luz que nos viene del sol a través del espacio en forma de radiación electromagnética. En la figura 1.1 se muestra otro ejemplo de propagación de una onda cuando sacudimos el extremo de una cuerda, la perturbación producida viaja por ella alcanzado todos sus puntos. Figura 1.1 Ejemplo de onda mecánica 1.1.1 El concepto de onda En los ejemplos anteriores se puede apreciar una característica común: una perturbación generada en un punto de inicio que se propaga en el espacio hasta alcanzar otro punto. En estas situaciones anteriores ha sido necesario aportar energía, por lo tanto al generar la perturbación se transmite energía. 3 C A P Í T U L O 1 • Teoría del sonido Así pues, podemos definir una onda como el fenómeno de transmisión de una perturbación de alguna propiedad de un medio en el espacio transportando energía. El medio en el que se transmite puede ser de diferente naturaleza: aire, agua o un trozo de metal, por ejemplo. Es de tener en cuenta que este transporte de energía por el medio nunca va acompañado de transporte de materia. Dentro de los tipos de onda y dependiendo del tipo de clasificación que apliquemos podemos considerar los siguientes: • Ondas materiales: en este tipo de ondas la perturbación se produce en un medio elástico (aire, agua, metal) y se transmiten gracias a la elasticidad del medio, es por ello que la velocidad de propagación dependa de sus características elásticas. En esta clasificación cabe destacar los ejemplos anteriormente citados de la onda producida al tirar una piedra al agua y la cuerda. • Ondas electromagnéticas: Son un caso especial de ondas, en las que no es necesario un medio material, sino que pueden transmitirse por el vacío. Las ondas electromagnéticas están producidas por las oscilaciones de un campo eléctrico en relación con un campo magnético asociado. Dentro de este arquetipo podemos encuadrar los ejemplos de las ondas de radio, las de TV, o las de un router Wi-Fi. Figura 1.2 Otro ejemplo de onda, al tirar un objeto al agua 1.1.2 Magnitudes de la onda Una vez vistos los ejemplos de ondas más característicos de nuestro entorno cotidiano y explicado su concepto, es hora de definir las magnitudes que caracterizan los tipos de ondas vistos previamente. Las magnitudes principales que describen una onda son las siguientes: 4 • Periodo: Es el tiempo que transcurre entre dos pulsos sucesivos de la onda, se representa con la letra T y se mide en segundos. • Longitud de onda: Se define como la distancia entre dos pulsos sucesivos en un medio determinado. La longitud de onda depende de la velocidad de C A P Í T U L O 1 • Teoría del sonido propagación de la perturbación en el medio. Se escribe con la letra griega λ (lambda) y se representa en metros. • Frecuencia: Esta magnitud es fundamental en el estudio de las ondas y también está relacionada con el periodo, puesto que se define como la inversa del mismo. Para hacernos una idea que resulte fácil, podemos entenderla como el número de oscilaciones que se producen en un segundo. Su medida es el Hertzio (Hz o s-1), en honor al físico alemán Heinrich Rudolf Hertz, aunque también se emplean múltiplos como el Kilohertzio (KHz) y el Megahertzio(MHz). Se expresa con la fórmula: f = • 1 T Amplitud: Se considera como la distancia máxima que separa un punto de la posición de equilibrio. (La distancia superior o inferior a la línea central de la forma de onda de la figura 1.3). Cuanto más grande sea la distancia o desplazamiento de esa línea central, más intenso será el nivel de la señal eléctrica, la variación de presión o el desplazamiento físico dentro del medio. Amplitud Amplitud (A) Amplitud (A) Tiempo Periodo (T) Figura 1.3 Parámetros de una onda senoidal 5 C A P Í T U L O 1 • Teoría del sonido 1.1.3 Ecuación matemática de la onda El movimiento ondulatorio supone la transmisión de una perturbación de un punto a otro sin transporte neto de materia. Por lo tanto, y como el objetivo es conocer dicho movimiento (sólo queda satisfecho cuando en cada momento se pueda conocer la perturbación que afecta a cada punto del medio por el que se propaga), ¿cómo podríamos establecer una ecuación matemática que defina el valor de dicha perturbación en función del tiempo? Tomando como ejemplo el caso de la cuerda y suponiendo que la propagación de la onda armónica es a una determinada velocidad (v) y que se mueve en dirección positiva del eje X como se muestra en la figura 1.4: y v x Figura 1.4 Pulso inicial Transcurrido un tiempo t, el pulso habrá recorrido una distancia: d=v·t Quedando en la siguiente posición: y d x Figura 1.5 Desplazamiento del pulso Por lo tanto, si la función: 6 C A P Í T U L O 1 • Teoría del sonido y = f(x) representa el pulso inicial, la función y = f(x – d) representará el pulso d, ya que la forma de la curva no varía. Se puede observar que para valores de x incrementados cierto valor d se obtiene el mismo resultado en la función. Sustituyendo d por su valor, resulta la función: y = f(x- vt) que corresponde a una onda que se propaga con velocidad v hacia la derecha a lo largo del eje X. Si se considera un tren de ondas armónico propagándose por la cuerda, la ecuación que describe la posición de cada punto de la cuerda en el instante inicial es de la siguiente forma: y = A · sen kx siendo A y k constantes. Si admitimos que a medida que transcurre el tiempo el tren de ondas se propaga con velocidad v hacia la derecha sin deformarse, la ecuación que describe el movimiento es de la siguiente forma: y(x,t) = A · sen k(x – vt) Figura 1.6 Onda inicial y propagada Al término k(x-vt) se le denomina fase. En ocasiones hay que añadir a esta fórmula la constante δ, que recibe el nombre de fase inicial. De ese modo, 7 C A P Í T U L O 1 • Teoría del sonido y(x,t) = A · sen[k(x – vt) + δ] sería entonces la ecuación que encierra toda la información acerca del movimiento ondulatorio. 1.2 El sonido como forma de onda El sonido es por tanto un fenómeno físico y como se ha visto anteriormente, es un movimiento ondulatorio en un medio elástico que generalmente es el aire, debido a cambios rápidos de presión generados por el movimiento vibratorio en el medio. Podríamos, entonces, clasificar el sonido como un tipo de onda que produce cierta sensación en el oído como consecuencia del movimiento ondulatorio en el aire, agua u otros medios. 1.2.1 Dominio del tiempo y frecuencia Una señal puede verse como la variación en el tiempo de una cantidad tal como voltaje o corriente. Como ejemplo consideramos la señal de voltaje sinusoidal dada por: v(t) = V · cos (2πFt + θ) V es el voltaje de pico, F es la frecuencia y θ es la fase relativa. Estos parámetros se indican en la figura 1.7. La onda es periódica con periodo T = 1/F, ya que: v(t) = v(t + T) V(t) V Amplitud (A) Tiempo T = 1/F Figura 1.7 Onda senoidal en el dominio del tiempo Esta descripción se denomina representación de la señal en el dominio del tiempo: la señal se ve como una función del tiempo. Esta es la forma más común de 8 C A P Í T U L O 1 • Teoría del sonido representar las señales y de observarlas en las aplicaciones de edición de sonido comunes. Sin embargo, existe otra manera de representación de una señal. Consideremos la forma de onda de la figura 1.7. Esta señal sinusoidal viene descrita por la amplitud V, la frecuencia F y la fase θ. Estos tres parámetros junto con el conocimiento de que la señal es sinusoidal son suficientes para dibujar la forma de onda del voltaje; la tripleta (V, F, θ) especifica completamente la señal. Si consideramos despreciable la fase, es decir, θ =0, trataremos entonces con la amplitud V y la frecuencia F; ahora el par (V,F) especifica completamente la señal. En esta interpretación F y V pueden tomar cualquier valor positivo y la señal puede representarse gráficamente como muestra la figura 1.8. (V,F) V Componente espectral 0 F = 1/T Figura 1.8 Representación espectral de una onda senoidal Esta es la representación en el dominio de la frecuencia. Con este punto de vista, una señal cosenoidal viene representada por una flecha vertical localizada en algún punto F del eje de frecuencias, y la altura de la flecha corresponde a la amplitud V. La representación en el dominio de la frecuencia es muy útil, ya que las señales más complejas pueden ser consideradas como una superposición (suma) de componentes sinusoidales con diferentes amplitudes, frecuencias y fases. Para las señales armónicas las frecuencias están relacionadas por números enteros. Si T es el período, entonces se dice que la forma de onda tiene una frecuencia fundamental de 1/T. La siguiente frecuencia más cercana contenida en la forma de onda es 2/T, denominada segundo armónico, y después viene el tercer armónico a la frecuencia 3/T, y así sucesivamente. Generalmente los distintos armónicos tienen amplitudes y fases diferentes. Para obtener una apreciación general del contenido en armónicos de una forma de onda compleja, es conveniente representar la señal gráficamente en el dominio de la frecuencia, como se ilustra en la figura 1.9. 9 C A P Í T U L O 1 • Teoría del sonido f 0 1/T 2/T 3/T 4/T Figura 1.9 Representación de las componentes espectrales de una onda armónica La altura de las flechas representa la fuerza de los distintos armónicos. Esta representación en el dominio de la frecuencia se denomina frecuentemente espectro de la señal; los distintos componentes en frecuencia que los constituyen se denominan componentes espectrales o líneas espectrales. 1.2.2 Teorema de Fourier Gracias al teorema de Fourier, desarrollado por el matemático francés Fourier (1807-1822) y completado por el matemático alemán Dirichlet (1829), es posible demostrar que toda función periódica continua, con un número finito de máximos y mínimos en cualquier período, puede desarrollarse en una única serie trigonométrica uniformemente convergente a dicha función, llamada serie de Fourier. Supongamos que una señal periódica se puede representar como: x(t) = A0 + A1cos(2π(t/T) + θ1) + A2cos(2π(t/T) + θ2) + … + Ancos(2π(t/T) + θn) o más concisamente como: ∞ x(t ) = A0 + ∑ An cos[2π (nt / T ) + θn ] n =1 Esto se conoce como la representación mediante series de Fourier de x(t). Nótese la relación con la representación en el dominio de la frecuencia: Las amplitudes An dan las alturas de las distintas líneas espectrales. Una representación alternativa se obtiene a partir de la relación trigonométrica cos(A +B), podemos entonces representar x(t) de la siguiente forma: ∞ ∞ n =1 n =1 x(t ) = A0 + ∑ an cos(2πnt / T ) + ∑ bnsen(2πnt / T ) donde an = Ancos(θn) y bn = -An sen(θn). 10 C A P Í T U L O 1 • Teoría del sonido Otra representación hace uso de la relación: cos A = e ja + e − ja 2 donde, definiendo C0 = A0, x(t) puede expresarse en términos de una única serie sobre todos los números enteros: x(t ) = ∞ ∑ Cne −( j 2π / T ) n = −∞ los términos an y bn reciben el nombre de coeficientes de Fourier y pueden obtenerse evaluando las integrales: a0 = 1T ∫ x(t )dt T0 an = 2 T ∫ x(t ) cos(2πnt / T )dt T 0 bn = 2 T ∫ x(t ) sen(2πnt / T )dt T 0 que se obtienen extrapolando lo sumatorios, es decir usando el concepto de integral definida, como suma de infinitos diferenciales de función entre dos límites que representan un intervalo. El coeficiente a0 corresponde al valor medio de la función en el período T. 11 C A P Í T U L O 1 • Teoría del sonido 1.2.3 Ondas armónicas y ruido Desde el punto de vista subjetivo, los sonidos que percibimos se pueden dividir en dos grandes grupos: sonidos armónicos y ruidos o sonidos no armónicos. Las ondas armónicas se caracterizan por tener componentes que se repiten periódicamente y que dan lugar a una serie de rayas espectrales en la transformada de Fourier, conocidas como armónicos. Este es el tipo de sonido que se genera como consecuencia de la vibración de un material elástico a la frecuencia de resonancia. Este sería el caso del sonido de una cuerda de piano. El ruido en cambio no tiene componentes armónicas y por tanto da lugar a una transformada de Fourier con componentes en todas las frecuencias. Según la distribución de amplitud de estas componentes se puede hablar de ruido blanco y ruido coloreado. El ruido blanco tiene igual amplitud en todas las frecuencias, mientras que el ruido coloreado tiene mayor peso de unas frecuencias que de otras. Este es el tipo de sonido que se genera como consecuencia de una interacción mecánica a una velocidad superior de la característica del medio, lo cual da lugar a una onda de choque. Este sería del caso del sonido de percusión. 12 C A P Í T U L O 1 • Teoría del sonido Figura 1.10 Ondas armónicas y ruido con sus respectivas transformadas 1.2.4 Propagación del sonido El efecto que producen la vibración de las partículas desplazadas como forma de onda en el aire y que percibe el oído es lo que denominamos sonido. Como mencionamos antes, el sonido es una onda mecánica, y aunque se puede propagar por otros medios, es necesario que se convierta en una perturbación acústica para poder ser percibida por el oído. Como ejemplo ilustrativo, imagine el lector los primeros modelos de teléfono en el que se conectaban dos vasos de plástico por medio de una cuerda tensa. Al comenzar a hablar uno de los interlocutores, el sonido se transforma en una vibración mecánica y consigue hacer vibrar el envase situado en el extremo opuesto. El sonido se propaga por los diferentes medios de forma longitudinal, es decir, el movimiento de las partículas ocurre en la dirección misma en la que la onda se propaga. Si estudiamos el caso de la propagación del sonido en el aire, éste se transmite como una onda de presión debido a la compresión y expansión de las partículas de oxígeno. Un ejemplo de este fenómeno podemos verlo ilustrado en la figura 1.11. 13 C A P Í T U L O 1 • Teoría del sonido Figura 1.11 Propagación de una onda sonora No debe confundirse la propagación del sonido con la propagación de una perturbación mecánica que da lugar a una señal acústica. Esto ocurre en medios diferentes al aire. Así pues, cuando por ejemplo golpeamos un metal alargado que produce sonido en un extremo y en el otro es la transformación acústica de la vibración mecánica, aquí es importante saber que no se transmite el sonido por el metal, sino que es una perturbación mecánica a través del metal. Como vimos en el apartado 1.1.1, tanto si se trata de una onda material, como si ésta es electromagnética, la perturbación se propaga de un punto a otro del espacio con una velocidad que depende de las características del medio. En ambos casos se produce un transporte de energía. La velocidad del sonido depende de las características del medio físico en las que se transmite la propagación. La velocidad del sonido es mayor en elementos sólidos que en elementos líquidos, y en los líquidos mayor que en los gaseosos. La velocidad del sonido depende de la siguiente fórmula: v= B ρ Siendo B el factor de compresión del medio y ρ la densidad. En general la velocidad del sonido dependerá de la densidad del material y su elasticidad. Así, la velocidad de propagación de la onda es menor cuanto mayor es la densidad, por ello, la velocidad en el agua es menor que en la madera, como podemos ver en la tabla 1.1. No obstante, también influiría aquí el factor de compresión, que explicaría entonces por qué la velocidad de una onda es mayor en el acero que en el agua, a pesar de tener mayor densidad. Material Velocidad Agua (20 ºC) 1.482 m/s Madera 3.900 m/s Acero Inoxidable 5790 m/s Tabla 1.1 Velocidad de propagación del sonido en diferentes medios 14 C A P Í T U L O 1 • Teoría del sonido La velocidad del sonido en el aire (a una temperatura de 20º) es de 344 m/s. Existe una ecuación propuesta por Newton y posteriormente modificada por Laplace que nos permite obtener la velocidad del sonido en el aire teniendo en cuenta la variable de la temperatura: V = 331.3 t 1+ 273.15 1.3 Percepción humana del sonido El sonido no sería una realidad si no fuera por la capacidad de captación por los sentidos del ser humano, en este caso el órgano encargado de transformar las vibraciones sonoras en el medio en respuestas nerviosas interpretables por el cerebro es el oído. Este será el objeto de estudio del presente apartado. 1.3.1 Funcionamiento del oído Una de las funciones principales del oído es convertir las ondas sonoras en vibraciones que estimulen las células nerviosas, para ello el oído tiene tres partes claramente identificadas. Estas partes están interconectadas y son el oído externo, el medio y el interno. Cada parte tiene funciones específicas dentro de la secuencia de procesamiento del sonido. Figura 1.12 Anatomía del oído humano 15 C A P Í T U L O 1 • Teoría del sonido Empecemos con cada una de estas partes: • Oído externo: Es la parte más externa e incluye las siguientes partes: o Pabellón auricular: Es la parte exterior del oído, es un cartílago plano y elástico que tiene forma del extremo de una trompeta y está cubierta de piel gruesa. Las partes principales del pabellón auricular son: el hélix que es el borde exterior, el antihélix que es la eminencia central del pabellón que termina en una elevación llamada antítrago o parte central, en la parte inferior. o Conducto auditivo externo: Es una especie de tubo que mide unos 2,5 cm de longitud situado en el hueso temporal; está compuesto por folículos pilosos, glándulas sebáceas que producen cerumen y glándulas de ovillo que son las responsables de dar el color a la cera. • o Membrana timpánica: Esta membrana vibra cuando es golpeada por ondas sonoras transmitidas por el aire y es la responsable de convertir las mismas en impulsos nerviosos que llegan al cerebro. Oído medio: o Trompa de Eustaquio: Conecta el oído medio con la faringe e iguala la presión entre las dos bandas de la membrana timpánica. o Martillo, Yunque y Estribo: Se encargan de transmitir al oído interno las vibraciones sonoras que llegan por el aire. Transforman las ondas sonoras en vibraciones mecánicas. Las vibraciones captadas por la membrana timpánica hacen que se mueva el martillo, que desplaza al yunque y al estribo, que es el hueso conectado a la membrana oval, recibiendo las vibraciones aumentadas en 5 dB. • o Ventana oval: Es la responsable de convertir los estímulos recibidos por los huesos anteriores del medio aéreo a un medio líquido Oído interno: o Cóclea: También llamado caracol. Como su mismo nombre indica, tiene forma de tubo espiral. Se encuentra lleno de líquido y posee la membrana de Reissner y la membrana basilar, donde reside el órgano de Corti, formado por células ciliadas que vibran a determinadas frecuencias. o Canales semicirculares: Son tres tubos de forma semicircular, uno de ellos se encuentra en posición horizontal y los otros dos en posición vertical. o Nervios auditivos: Son los responsables de transmitir la información sonora al cerebro. En la figura 1.13 puede observarse la relación de las funciones del oído con un procesamiento computerizado del mismo: 16 C A P Í T U L O 1 • Teoría del sonido Oído externo Oído medio Oído interno Pabellón auricular + Conducto auditivo + Membrana timpánica Martillo + Yunque + Estribo Vestíbulo+ Cóclea Preamplificador procesador Micrófono de alta calidad Analizador de frecuencias Figura 1.13 Modelo equivalente del oído humano 1.3.2 Cualidades del sonido En apartados anteriores hemos visto los parámetros del sonido desde el punto de vista físico. Sin embargo, desde el punto de vista de la percepción auditiva, hay otros parámetros que resultan relevantes: • Intensidad: Se define como la energía por unidad de área. Normalmente es la medida de la intensidad del sonido en el aire en la posición donde se encuentra el oyente. El oído humano responde de manera logarítmica a la intensidad del sonido y se suele medir en decibelios, concepto que se estudiará en la siguiente sección. La unidad básica son los vatios/m2 o vatios/ cm2. • Tono: Es la respuesta del oído a la frecuencia del sonido. Con el tono se percibe si los sonidos son graves, agudos o medios. La mayoría de los sonidos que escuchamos están compuestos de varias frecuencias, por tanto, el tono que se percibe depende de la frecuencia dominante. • Timbre: Se determina principalmente por el contenido armónico o las características dinámicas del sonido como son: el vibrato, la componente de ataque etc. El timbre permite distinguir sonidos, como por ejemplo, diferenciar una misma nota producida por dos instrumentos musicales diferentes. • Duración: Es la duración de la vibración del sonido. 17 C A P Í T U L O 1 • Teoría del sonido 1.3.3 Espectro audible e intensidad sonora (Decibelios) El espectro audible del ser humano se encuentra en el rango de frecuencias desde los 20 a los 20000 Hertzios (Hz). Los sonidos por debajo de los 20 Hz se les denomina infrasonidos o subsónicos, y respectivamente, a los que tienen una frecuencia superior a los 20000 Hz se les llama ultrasónicos. A las frecuencias se las puede clasificar según su tonalidad, por lo tanto, a mayor frecuencia mayor tonalidad. Así existen tonos graves, medios y altos. Los sonidos graves caen dentro del rango de 20 a 300 Hz, los medios de 300 a 2000 Hz y los agudos de 2000 a 20000Hz. En cuestión de frecuencia el oído también funciona en escala logarítmica, y esta es la base de la armonía musical. Dos notas musicales se consideran armónicas cuando la frecuencia de una es un múltiplo de la otra. Una octava es el intervalo entre dos notas cuya frecuencia fundamental tiene una relación de dos a uno. Dentro de una octava hay siete notas básicas, siendo la octava nota del doble de frecuencia que la primera, de ahí su nombre. El oído nos hace percibir como equiespaciadas las notas dentro de una octava, a pesar de que la distancia en frecuencia es diferente según la octava que se considere. El espectro audible se puede subdividir en octavas. En la siguiente tabla se puede observar una relación entre el número de octava y su frecuencia: 1ª octava 16 – 32 Hz 2ª octava 32 - 64 Hz 3ª octava 64- 125 Hz 4ª octava 125 – 250 hz 5ª octava 250 – 500 Hz 6ª octava 500 – 1000 Hz 7ª octava 1000 – 2000 Hz 8ª octava 2000 – 4000 Hz 9ª octava 4000 – 8000 Hz 10ª octava 8000 – 16000 Hz 11ª octava 16000 – 32000 Hz Tabla 1.2 Rango de frecuencias de las octavas musicales La primera y la última octava son prácticamente inaudibles. También la capacidad vocal se puede medir en octavas, siendo el rango medio de una persona de 3 octavas. Respecto a la intensidad sonora, depende de la amplitud de oscilación, de la potencia de la fuente y de la forma en que ha sido transmitida, es decir, el medio físico. La intensidad sonora se mide en decibelios (dB), que es la unidad utilizada para medir el nivel presión sonora (SPL). El término decibelio significa 1/10 de un belio, unidad de medida de transmisión telefónica que recibió el nombre gracias a Alexander Graham Bell, inventor del teléfono. 18 C A P Í T U L O 1 • Teoría del sonido La sensación sonora de intensidad se agudiza para sonidos débiles, y su sensibilidad disminuye para sonidos fuertes, es decir, los decibelios siguen una relación exponencial, que es la que tiene el oído humano. En la figura 1.14 se presentan los umbrales de la tolerancia de los sonidos en decibelios: Figura 1.14 Sensibilidad del oído humano La referencia de los 0 dB SPL es el umbral de audición, que es la menor cantidad de sonido en el aire perceptible por el oído humano emitida a 1000 Hz. En la siguiente ecuación se muestra la relación del valor del nivel de intensidad sonora: I Nivel = 10·log Iref que, por lo tanto, se puede expresar de la siguiente forma: P Nivel = 10·log Pr ef Como la referencia es 2 · 10 -5 Pa, resulta que los decibelios SPL valen medidos en Pa: P NivelSPL = 20·log -5 2 · 10 19 C A P Í T U L O 1 • Teoría del sonido En la siguiente tabla se muestra una correlación de distintos niveles de SPL de presión sonora con sonidos del medio: Decibelios Caso real Despegue de un avión 120 Umbral de dolor 110 Martillo neumático 90 Interior de una oficina 60 Conversación normal 50 Habitación en silencio 40 Al aire libre en silencio 20 Umbral inferior de audición 0 Tabla 1.3 Niveles sonoros de diversos eventos 1.4 Generación de sonido analógico y digital El sonido es resultado de vibraciones producidas en alguna fuente de tipo material (como un piano) o una fuente electrónica (como un sintetizador o una guitarra eléctrica). Ambas son diferentes en la forma de generación de la forma de onda, pero aún así, comparten características comunes que comentaremos a continuación. 1.4.1 Instrumentos musicales convencionales En este apartado tratamos los instrumentos fabricados con materiales como la madera o el metal. Vamos a tratar la física de los instrumentos de percusión, los de viento y los de cuerda como ejemplos más significativos. Instrumentos de percusión: El ejemplo más característico es el tambor. La peculiaridad principal de este instrumento es que el sonido se genera golpeando un mazo o baquetas contra una membrana, llamada comúnmente parche, que cubre la abertura de una caja de resonancia generalmente cilíndrica. Los instrumentos de percusión se basan principalmente en la vibración y en la resonancia. Cuando se golpea la membrana se produce una deformación de la misma que genera una vibración del aire que hay dentro del mismo. El cuerpo del tambor empieza a resonar, junto con la membrana, produciendo un sonido alto y grave procedente de su estructura. La frecuencia de resonancia de la caja del tambor se define según la ecuación: 20 C A P Í T U L O 1 • Teoría del sonido 0.764 F · f = D µ 1/ 2 donde F es la tensión de la membrana por unidad de circunferencia en N/m (newtons por metro), D es el diámetro de la membrana y µ es la densidad de área de la membrana medida en kg/m2. Para los xilofones, marimbas u otros instrumentos que incorporen platos vibrantes, se usa la siguiente ecuación para obtener la frecuencia de resonancia: f = 1.03·kv L2 donde L es la longitud de la barra, v es la velocidad del sonido en el objeto y k es una constante que depende del objeto. Instrumentos de viento: Los instrumentos de viento generan sonido por medio de vibraciones producidas en el interior de tubos. Los cambios de frecuencia del sonido se producen como consecuencia de acortar o alargar la longitud de los tubos, cerrando y abriendo pequeños orificios creados en los mismos. Existen tres categorías de instrumentos de viento, en atención al material constructivo y a la forma de generar la vibración: metal (ej. trompeta), madera (ej. flauta) y órganos. La ecuación para determinar la longitud de onda en un instrumento de viento donde el tubo está abierto por ambos extremos es: λ= 2L n donde n ≤ 1 y L es la longitud del tubo. y la ecuación para un instrumento de viento abierto por sólo por un extremo es: λ= 4L (2n + 1) donde n ≤ 0 y L es la longitud del tubo. 21 C A P Í T U L O 1 • Teoría del sonido Instrumentos de cuerda: Los instrumentos de cuerda generan el sonido por vibración de una cuerda tensada, que resuena en una caja de resonancia. Estos instrumentos están construidos de diferentes materiales para producir diferentes tonalidades. En todos los instrumentos de cuerda, la cuerda está fijada en los extremos del instrumento y es golpeada, frotada o pinzada para producir una onda sonora que es amplificada posteriormente en el cuerpo del instrumento. De la forma de actuar sobre la cuerda se deriva la clasificación de estos instrumentos: de cuerda frotada (ej. violín), de cuerda pulsada (ej. guitarra) o de cuerda percutida (ej. piano). A continuación se deduce la frecuencia de vibración de una cuerda tensada. La velocidad de una onda viajando por una cuerda vibrando, y la tensión de la cuerda están relacionados por la siguiente ecuación: v = λf T v= m L 1/ 2 = λf donde T es la tensión en Newtons, m es la masa de la cuerda y L la longitud de la misma. Por lo tanto: 1/ 2 T m L f = λ Esta frecuencia es conocida como la frecuencia fundamental y tiene una longitud de onda igual a dos veces la longitud de la cuerda, debido a esto los nodos o puntos cero de desplazamiento de la onda son los finales de la cuerda del instrumento (figura 1.15). Debido a esto incluiría longitudes de onda que son iguales a la longitud de la cuerda, (primer armónico), 2/3 de la longitud de la cuerda (segundo armónico), 2/4 de la longitud de la cuerda (tercer armónico), y así sucesivamente. Es importante conocer que el sonido de un instrumento de cuerda será diferente atendiendo al cuerpo del instrumento que hará que resuene a diferentes frecuencias. 22 C A P Í T U L O 1 • Teoría del sonido Figura 1.15 Vibración de una cuerda 1.4.2 Síntesis analógica En esta forma de síntesis entendemos la generación de sonido sin electrónica digital, conectando varios módulos en cascada para obtener el sonido final. El elemento principal en un sintetizador, ya sea analógico o digital, es el oscilador, que es un sistema que genera una onda periódica con una determinada forma (cuadrada, sinusoidal, triangular, diente de sierra, etc.). Se puede clasificar los tipos de síntesis analógica en los siguientes tipos: • • • • Síntesis aditiva: Consiste en generar el sonido mediante sucesivos enriquecimientos del espectro de la onda, es decir, se van añadiendo armónicos hasta conseguir la forma de onda deseada. Síntesis sustractiva: Se parte de una forma de onda muy rica en armónicos, por ejemplo del resultado de una síntesis no lineal, y desde ahí vamos eliminando armónicos hasta quedarnos con una forma de onda más básica y adecuada a nuestras necesidades. Síntesis por modulación de amplitud (AM): Consiste en utilizar una señal portadora a una determinada frecuencia e ir modulando su amplitud mediante otra señal, por lo general, múltiplo de la primera. La forma de onda resultante se le denomina señal moduladora. Por ende, se consiguen formas de ondas con diferentes timbres partiendo de formas de onda más primitivas. Síntesis por modulación en anillo (RM): En este caso se trata de una modulación en amplitud, pero se multiplica la señal portadora por la moduladora, generando de esta forma sonidos más peculiares que los de síntesis por modulación de amplitud (AM). Las técnicas citadas anteriormente se las califica de técnicas de síntesis lineal, pues existe una relación lineal entre el espectro de las frecuencias iniciales (moduladora y portadora, por ejemplo) y el espectro de salida. La síntesis analógica está basada en módulos, todo en síntesis analógica es modular. En la figura 1.16 tenemos el diagrama de bloques de un sintetizador analógico: 23 C A P Í T U L O 1 • Teoría del sonido Figura 1.16 Diagrama de bloques de un sintetizador analógico A continuación se describen con más detenimiento cada una de sus partes: Oscilador Un oscilador es un circuito electrónico capaz de generar una forma de onda periódica a una determinada frecuencia. Existen osciladores controlados por tensión (cuya frecuencia de oscilación es controlada por el voltaje de un circuito) y osciladores digitales (que son generados de por circuitería digital). En este caso nos centraremos en los primeros. Las formas de onda más utilizadas en música electrónica han sido las cuadradas y las de diente de sierra, como se ve en la siguiente figura: Figura 1.17 Onda de diente de sierra (izquierda) y onda cuadrada (derecha) También se utilizan otros tipos de onda como la triangular y la onda de ruido (onda que no posee tonalidad, es aleatoria, no posee periodo y se le denomina ruido blanco). Las ondas triangulares son apropiadas para generar timbres parecidos a los instrumentos de viento, mientras que la aleatoria es más apropiada para los instrumentos de percusión. Figura 1.18 Onda triangular (izquierda) y aleatoria (derecha) Para crear un generador de onda triangular con circuitería analógica se utiliza el circuito que se describe en la figura 1.19. El condensador C se carga mediante una fuente de corriente constante, con lo cual la tensión en sus terminales crece linealmente. 24 C A P Í T U L O 1 • Teoría del sonido Dicha tensión se aplica a un amplificador operacional que funciona como comparador. Cuando la tensión del condensador supera la tensión de referencia Vref el comparador activa el transistor, el cual descarga el condensador y el ciclo vuelve a empezar. Si la señal triangular se aplica a un segundo comparador con una tensión de referencia variable, se consigue una onda de ancho de pulso variable (PWM). En la siguiente figura podemos ver una implementación analógica para un oscilador de diente de sierra y cuadrada: Figura 1.19 Generador de formas de onda básicas (diente de sierra y cuadrada) Generadores de envolvente Un generador de envolvente es un circuito que genera una señal que permite controlar un parámetro dentro de un sistema de síntesis. La señal envolvente se suele usar para modular la amplitud de la señal armónica, y de esta manera simular las variaciones de amplitud de un instrumento de cuerda. Otros tipos de envolvente se utilizan para modular el tono o pitch de la señal. Normalmente, los generadores de envolvente, son del tipo ADSR (Attack, decay, sustain, release) o traducido al español (ataque, decaimiento, sostenido, liberación) como se puede ver en la siguiente figura: 25 C A P Í T U L O 1 • Teoría del sonido Figura 1.20 Diagrama de una envolvente de volumen En la siguiente figura se puede ver un circuito utilizado para generador de envolvente tipo ADSR Figura 1.21 Esquema eléctrico de un generador de envolvente de volumen Amplificador Los amplificadores que utilizan los sistemas analógicos son del tipo VCA o amplificadores controlados por tensión que tienen la peculiaridad que su ganancia puede ser controlada por un voltaje adicional. El VCA es el complemento indispensable para producir una envolvente de amplitud. Para implementar un VCA se suelen usar amplificadores operacionales de transconductancia, aunque también se puede implementar con componentes discretos. En la siguiente figura se observa la circuitería para implementar un VCA. 26 C A P Í T U L O 1 • Teoría del sonido Figura 1.22 Amplificador de amplitud controlada Filtro Los filtros son elementos indispensables en la síntesis analógica; aunque estos conceptos se tratarán más en profundidad en el capítulo 7. Hay varios tipos de filtro como veremos a continuación: Filtro paso-bajo Filtro paso-banda Figura 1.23 Tipos de filtro Filtro paso-alto Filtro elimina-banda 27 C A P Í T U L O 1 • Teoría del sonido El filtro más utilizado en cuestión es el paso-bajo, pues es el más fácil de implementar. Tiene dos características fundamentales: la frecuencia de corte y la pendiente de filtrado. Algunas implementaciones de filtro paso-bajo incorporan un filtro resonante para mejorar la pendiente de corte, un cuyo caso hay un parámetro más que controlar. La frecuencia de corte debe ser ajustable mediante un voltaje de control, la de resonancia también, aunque en este esquema no se varía con un voltaje sino con una resistencia, mientras que la pendiente de filtrado viene determinada por el número de polos del circuito. Valores típicos para la pendiente 2 polos: 12 dB/octava 3 polos: 18 dB/octava 4 polos: 24 dB/octava 5 polos: 30 dB/octava … Vemos que los decibelios/octava se van incrementando de 6 en 6 a partir de 12 dB/octava: cada polo introduce un incremento de 6 dB/octava en la pendiente. A mayor pendiente, más selectivo es el filtro. Valores comunes para filtros es de 12 dB/octava (2 polos) y 24 dB/octava (4 polos). Respecto a la implementación del circuito se puede decir que la configuración se le denomina Filtro de Estado Variable (State Variable Filter) ya que permite la configuración independiente tanto de la frecuencia de corte como de la resonancia o factor de amortiguamiento. Figura 1.24 Esquema eléctrico de un filtro sintonizable La frecuencia de corte se controla mediante un voltaje, mientras que la resonancia se controla con un simple potenciómetro. 28 C A P Í T U L O 1 • Teoría del sonido 1.4.3 Técnicas de síntesis digital Dentro de la síntesis digital consideramos la generación de sonido utilizando un sistema electrónico digital, que puede ser un circuito dedicado o un sistema procesador controlado por software. Hay que destacar que muchas de las técnicas utilizadas en la síntesis analógica son utilizadas también en la síntesis digital. La síntesis digital tiene varias ventajas sobre la analógica: • • • Se puede reutilizar el mismo circuito para varios sonidos, simplemente cambiando la configuración. Es menos sensible al ruido acoplado, porque todo el procesamiento es digital. Se pueden hacer filtros y efectos difíciles de fabricar en circuitería analógica. A continuación destacamos las técnicas más comunes de síntesis digital: Síntesis aditiva Como vimos en la sección anterior, la síntesis aditiva consiste en generar un sonido periódico más complejo resultante de la suma de ondas sinusoidales más elementales de frecuencia múltiplo de una frecuencia base. Para obtener una forma de onda rica, son necesarios muchos armónicos. Actualmente esto es fácilmente realizable vía software, pero en los inicios plasmarlo con circuitería digital (hardware) requería un gran número de osciladores y sumadores y por lo tanto, encarecía en exceso el sistema. En la siguiente figura se puede observar un ejemplo típico de síntesis aditiva digital por medio de la suma de dos formas de onda básicas: Figura 1.25 Síntesis aditiva de formas de onda La onda resultante mantiene la frecuencia del componente más grave, pero con el timbre alterado. 29 C A P Í T U L O 1 • Teoría del sonido Modulación de frecuencia (FM) Una de las formas de poder realizar síntesis de sonido es por medio de la modulación, que consiste en variar un parámetro de la señal portadora con respecto a la señal moduladora, generando una señal modulada. Para la síntesis FM se hace oscilar la frecuencia de la onda portadora. Para esta síntesis sólo se utilizan de dos a seis osciladores, mientras que en la síntesis aditiva se requiere un oscilador para cada onda. Por ende, la síntesis FM es más rica en calidad que la síntesis aditiva, ya que puede generar ondas complejas que contengan múltiples frecuencias con tan sólo dos osciladores. La historia de este modelo de síntesis tiene su origen en el famoso compositor John Chowning y en el sintetizador DX7 fabricado por la casa Yamaha: Figura 1.26 Sintetizador Yamaha DX7 Yamaha compró los derechos del algoritmo de síntesis FM para construir su modelo de sintetizador que puede apreciarse en la imagen anterior. En el funcionamiento de la síntesis FM, la distribución de los armónicos de una onda sinusoidal simple modulada por otra señal sinusoidal puede ser representada con las funciones de Bessel. La síntesis FM es una forma de síntesis no lineal. Comienza con un oscilador generando una onda portadora con la frecuencia Fc y otra con una señal moduladora, con una frecuencia Fm que se aplica para cambiar o modular la frecuencia de la portadora. Si la amplitud del modulador es 0, la frecuencia de salida de la señal portadora es simplemente Fc. De otra forma, la amplitud de la señal moduladora causa que la señal de la portadora oscile por encima o por debajo de Fc. En otras palabras, cuanto más fuerte es la señal moduladora, más cambia la frecuencia de la portadora. Como ejemplo ilustrativo, supongamos que Fc es 1000 Hz. Si la amplitud de la modulación es 100 Hz se consigue que la señal de la portadora oscile entre 900 Hz y 1100 Hz, esto es, 100 Hz en cada dirección. Esto es lo que se conoce como “desviación”. A la misma vez, la frecuencia de la señal moduladora causa bandas laterales (side bands) que aparecen en frecuencias por encima y por debajo de la frecuencia portadora. Por lo tanto, para cada componente de frecuencia en la señal moduladora, aparece una banda lateral superior por encima de Fc y una banda inferior por debajo de Fc. Una señal compleja moduladora (conteniendo más armónicos que una señal 30 C A P Í T U L O 1 • Teoría del sonido sinusoidal) creará bandas laterales correspondientes a cada una de sus componentes sinusoidales. La desviación es responsable de la distribución espectral de potencia de la señal de salida. Cuando la desviación es cero, toda la potencia se concentra en la frecuencia portadora. A medida que aumenta la desviación la potencia se reparte entre las frecuencias que rodean a la portadora. La relación entre la desviación y la frecuencia moduladora se denomina índice de modulación (I = d / fm). Esta relación controla la riqueza espectral del sonido. Si se actúa simultáneamente sobre la profundidad de modulación y sobre el espectro de la señal moduladora se consigue una gran riqueza armónica en la señal modulada. Esta es la potencia de la síntesis FM. β=1 β=5 β=25 Figura 1.27 Representación temporal de una señal modulada en FM, en función del índice de modulación. Puede consultarse más información sobre el algoritmo FM en la siguiente URL: http://the-all.org/tx81z/fm_overview.html y http://www.rfcafe.com/references/electrical/frequency-modulation.htm 31 C A P Í T U L O 1 • Teoría del sonido Síntesis por tabla de ondas Este es el sistema utilizado por la mayoría de los sintetizadores digitales modernos. Parten de pequeños fragmentos de sonidos digitalizados y almacenados en memoria. Esta síntesis también llamada de tabla de ondas o Wavetable se utiliza cuando no recurre directamente a un muestreo del sonido a emitir. Un sintetizador General MIDI (GM) deberá contener suficientes fragmentos para representar los 128 instrumentos y los 59 sonidos de percusión. Las técnicas de síntesis por tabla de onda más utilizadas son las siguientes: • Generación en bucle: es la técnica más primitiva y se basa en la repetición (looping) de muestras de sonido pregrabadas para producir una señal original. Para ello se dividen y almacenan las dos partes de la mayoría de los sonidos producidos por instrumentos: la sección de “ataque” que se reproduce entera y la de sostenimiento que se reproduce en bucle cada vez con menor intensidad siguiendo un patrón preestablecido. Finalmente el sonido se completa con una sección de liberación donde se termina de bajar la amplitud hasta 0 de manera más rápida imitando el desvanecimiento natural del sonido. Este sistema tiene cierta complejidad si se intentan utilizar efectos como coro o reverberación. Por otro lado, es necesario un tratamiento adicional de la señal para mejorar la relación señal/ruido o preparar la señal para su edición. 32 • Desplazamiento de tonos: con esta técnica se puede lograr que a partir de una muestra de sonido que representa una nota determinada se puedan generar otras notas del mismo instrumento. Para conseguir esto se aplican diferentes filtros a la señal original que junto con un aumento o reducción de la frecuencia de reproducción de la señal da lugar a la nueva nota. Este sistema es más fácil de implementar por software y es el que utilizan muchos VST (Instrumentos Virtuales Digitales) comerciales, como el de la figura 1.28. • Interpolación: esta técnica intenta eliminar el problema de almacenamiento en memoria, reduciendo la memoria destinada al muestreo de sonidos. En este caso los sonidos pueden estar grabados a una frecuencia relativamente baja. De esta forma cuando la señal sea enviada al conversor D/A (digital-analógico) el sintetizador puede encontrarse con que debe enviarle posiciones de memoria que son decimales (como, por ejemplo, enviarle la posición 4,5 de la memoria). Este problema se resuelve mediante la interpolación de los valores adyacentes. Con este método se puede reconstruir mejor la señal original y aumentar considerablemente la calidad del sonido. • Diezmado: es el efecto contrario a la interpolación. La señal es muestreada a varias veces por encima de la frecuencia necesaria para poder afinar mucho mejor los problemas de cambio de tono al editar la misma o realizar bucles con ella. El único problema de este método es el excesivo espacio de memoria que requiere. C A P Í T U L O 1 • Teoría del sonido Los sintetizadores actuales tienen una memoria ROM de hasta 64MB para el almacenamiento de muestras y las tarjetas de sonidos 4 y 8MB respectivamente. En el caso de los sintetizadores hay ciertas garantías de que la calidad del sonido será aceptablemente buena. Figura 1.28 Software VST con síntesis de tabla de ondas con desplazamiento de tonos. 33 C A P Í T U L O 1 • Teoría del sonido 1.5 Representación del sonido 1.5.1 Conversión analógico / digital Debe de ser posible representar el sonido que se encuentra en el ambiente o procedente de líneas de entrada (line-in) en un formato numérico inteligible para el ser humano y sobre todo procesable por computadora, de esta manera, es posible capturar sonido del medio con micrófonos y transformar inicialmente esas vibraciones en el aire en impulsos electromagnéticos (señal analógica) y finalmente, a través de un proceso electrónico de conversión, obtener una señal o secuencia de información digital procesable por software. A continuación tratamos el proceso de conversión de analógico a digital. 1.5.1.1 Teorema de Nyquist Para obtener muestras de una señal de entrada analógica se utiliza el teorema de Nyquist-Shannon, el cual postula que la frecuencia de muestreo debe ser dos veces superior a la frecuencia máxima de la señal a muestrear. Frecuencia muestreo = 2 x Frecuencia máxima señal analógica de entrada En la siguiente figura se puede observar el proceso de muestreo: Figura 1.29 Señal analógica muestreada En la práctica se suele elegir una frecuencia de muestreo ligeramente superior a la frecuencia máxima de la señal analógica. Así por ejemplo, si queremos representar una señal con una frecuencia 20000 Hz con calidad de CD tendremos que capturar la señal con una frecuencia de muestreo de 44100 Hz o 44,1 Khz. 1.5.1.2 Proceso de conversión Para poder realizar la conversión analógico digital se hace preciso la utilización de un tipo de circuito especial llamado ADC (Analog-to-Digital Converter o Conversor Analógico Digital). Dicho circuito realiza los siguientes procesos: Muestreo de la señal analógica: El primer paso para convertir una señal analógica en digital es muestrearla (sampling), es decir, tomar valores sucesivos de la amplitud de la señal a intervalos regulares de tiempo. Con este proceso la señal continua pasa a ser discreta en el tiempo, si bien no en amplitud. Como hemos explicado anteriormente estas muestras son 34 C A P Í T U L O 1 • Teoría del sonido tomadas con una frecuencia determinada a la que se denomina frecuencia de muestreo y por lo tanto a mayor frecuencia de muestreo mayor calidad y fidelidad tendrá la señal digital resultante. Durante el proceso de muestreo se asignan valores numéricos equivalentes a la tensión la amplitud de la señal, con la finalidad de realizar a continuación el proceso de cuantización. Voltios 8 7 6 5 4 3 2 1 0 Tiempo Figura 1.30 Principio básico del muestreo Cuanto mayor sea el número de muestras tomadas, mayor será también el ancho de banda necesario para transmitir una señal digital, requiriendo también un espacio mucho mayor para almacenarla en un CD o un DVD. Cuantización: Posteriormente al proceso de muestreo se realiza el proceso de cuantización de la señal analógica de entrada. Para conseguir este propósito, los valores continuos de señal se convierten en una secuencia de valores numéricos decimales discretos que corresponden a los diferentes niveles de voltaje de la señal de entrada. Voltios 8 7 6 5 4 3 2 1 0 Tiempo Figura 1.31 Cuantización de amplitud 35 C A P Í T U L O 1 • Teoría del sonido Codificación La codificación permite asignarle valores numéricos binarios equivalentes a los valores de tensiones o voltajes que conforman la señal eléctrica analógica original, como puede verse en la siguiente figura: Voltios 8 7 6 5 4 3 2 1 0 Tiempo 001 010 001 011 010 100 101 100 101 011 Figura 1.32 Codificación de las muestras (PCM) En el proceso de codificación es importante saber cuántos bits asignamos a esos valores de amplitud de la señal muestreada y cuantificada. Así si utilizamos valores de n bits para representar la señal podremos codificar un total 2n – 1 valores de amplitud diferentes. Por ejemplo, en un byte (8 bits) podemos representar un total de 28 – 1 = 256 -1 valores diferentes de amplitud, el problema es que la calidad del sonido no será tan aceptable. Por esto, en la mayoría de las aplicaciones reales se utiliza un tamaño de palabra de 16 bits, pudiendo por lo tanto representar un total de 216 – 1 = 65536 – 1 valores diferentes, obteniendo una señal resultante de mayor calidad. En la siguiente tabla se muestra una relación de formatos de frecuencias de muestreo y bits para representar los formatos de sonido más conocidos: Tipo formato Frecuencia muestreo Resolución Calidad CD 44100Hz 16bits Calidad Radio 22Khz 8bits Calidad teléfono 11Khz 8bits Tabla 1.4 Niveles de muestreo según la calidad y ancho de banda Dentro del proceso de codificación podemos considerar diferentes tipos de formato, entre ellos destacan: PCM El formato PCM (en inglés Pulse Code Modulation) es un formato o procedimiento de modulación para transformar la señal analógica muestreada y cuantificada en una formato de bits. Este formato es ampliamente utilizado, así podemos verlo aplicado a los teléfonos digitales, el audio digital en ordenadores (ampliamente tratado en este estudio) y el formato Red Book de los CD de audio. 36 C A P Í T U L O 1 • Teoría del sonido PAM El formato PAM o modulación por amplitud de pulsos (Pulse AmplitudModulation) es la más sencilla de las modulaciones digitales. Consiste en cambiar la amplitud de la señal (de frecuencia fija) en función del símbolo a transmitir. Dichas amplitudes pueden ser reales o complejas. Si representamos las amplitudes en el plano complejo tenemos lo que se llaman constelaciones de señal. En función del número de símbolos o amplitudes posibles se llama a la modulación NPAM. Así podemos tener 2PAM, 4PAM, 260PAM. De la correcta elección de los puntos de la constelación (amplitudes) depende la inmunidad a ruido (distancia entre puntos). Figura 1.33 Diferentes formatos de codificación 1.5.1.3 El problema del aliasing Un riesgo que puede existir durante la conversión analógico-digital es el aliasing, que se produce cuando la señal que se muestrea tiene componentes de frecuencia por encima de fs/2 o frecuencia de Nyquist. Para evitar este problema se utiliza un filtro paso-bajo, que elimina todas las frecuencias que están por encima de fs/2. En la práctica como los filtros no son perfectos hay que intentar que la frecuencia de Nyquist esté un poco por encima de la máxima frecuencia que se desea muestrear. La consecuencia del aliasing es que las frecuencias que están por encima de la frecuencia de Nyquist aparecen desplazadas en frecuencia dentro del espectro muestreado, como se muestra en la figura 1.34. 37 C A P Í T U L O 1 • Teoría del sonido Figura 1.34 Efecto del aliasing 1.5.2 Conversión digital / analógico El objeto de este tipo de convertidor es transformar una señal discreta en tiempo y frecuencia en una señal continua, en otras palabras, convertir una secuencia de códigos binarios en una señal eléctrica analógica. Como se vio en la sección anterior, la representación espectral de una señal muestreada es un conjunto de réplicas del espectro de la señal original, que se repiten en múltiplos de la frecuencia de muestreo. Al convertir la señal del dominio digital al analógico, estas réplicas aparecerán a menos que se disponga un filtro paso bajo a la salida del conversor D/A. Este filtro paso bajo es necesariamente analógico y puede resultar caro y complicado si la frecuencia máxima de la señal está muy cerca de la frecuencia de Nyquist. Sin embargo existen técnicas digitales que simplifican la reconstrucción de la señal y el diseño de dicho filtro, a cambio de una mayor complejidad en la parte digital. La técnica más comúnmente utilizada en los conversores D/A de audio y vídeo es el oversampling. Esta técnica consiste en aumentar la frecuencia de muestreo de la señal, manteniendo intacta la representación espectral. Como consecuencia, la frecuencia máxima de la señal se aleja de la frecuencia de Nyquist, permitiendo el uso de filtros analógicos más sencillos. Para aumentar la frecuencia de muestreo de la señal se ha de interpolar con muestras de valor cero. Un filtro interpolador digital transforma estas muestras nulas en valores intermedios entre las muestras originales. A pesar del aumento de complejidad en el dominio digital, el resultado merece la pena desde un punto de vista práctico, pues el área de silicio ocupada por el filtro digital es mucho menor que el espacio de un filtro analógico de orden elevado, con la ventaja 38 C A P Í T U L O 1 • Teoría del sonido adicional de que el filtrado digital no está sujeto a las tolerancias de los componentes analógicos. filtro analógico banda imagen t 0 fs/2 fs f interpolación t filtrado digital filtro analógico banda imagen t 0 f’s/2 f’s f Figura 1.35 Comparación entre la conversión D/A básica (arriba) y la conversión con oversampling (abajo) El desarrollo del oversampling ha evolucionado paralelamente al de los convertidores D/A sigma-delta, también conocidos como one bit stream. Estos convertidores no reconstruyen la señal analógica desde cero para cada muestra, sino que suman o restan la diferencia necesaria para que la señal alcance el nivel deseado. Esta diferencia se muestrea con un único bit de resolución, de ahí el nombre one bit stream. Esta simplificación se compensa con una mayor frecuencia de muestreo, pues para poder reproducir grandes variaciones de la señal en saltos de 1 LSB se requiere que su valor se actualice mucho más rápido que la frecuencia original de muestreo. Sin embargo las etapas analógicas del convertidor D/A son más sencillas, lo cual reduce su consumo. 39 C A P Í T U L O 1 • Teoría del sonido Bibliografía y referencias Libros Física COU – A.Candel, J.Satoca, J.B. Soler, F.Tejerina, J.J. Tent. Editorial Anaya, 1990. Apuntes Redes 3º IT Informática de Sistemas – Universidad de Murcia, 1997. Técnicas de Grabación modernas – David Miles Huber, Robert E. Runstein, 6ª edición. Editorial Omega, 2007. Diseño y desarrollo Multimedia, Sistemas, Imagen, Sonido y Vídeo – Manuel-Alonso Castro Gil, Antonio Colmenar Santos, Pablo Losada de Dios, Juan Peire Arroba. Editorial Ra-ma, 2002. Tecnología Básica del Sonido I – Ignasi Cuenca David, Eduard Gómez Juan. Editorial Paraninfo, 2006. La Web Wikipedia: Concepto de sonido: http://es.wikipedia.org/wiki/Cualidades_del_sonido Funcionamiento del oído: http://es.wikipedia.org/wiki/O%C3%ADdo Otros URL: The Physic of Instruments http://www.mrfizzix.com/instruments/ Sistema auditivo humano: http://www.ehu.es/acustica/espanol/fisiologia1/siaues/siaues.html Síntesis analógica musical: http://sam.atlantes.org/ – Avelino Herrera Morales Técnicas de síntesis digital: http://en.wikipedia.org/wiki/Frequency_modulation_synthesis http://es.wikipedia.org/wiki/S%C3%ADntesis_mediante_tabla_de_ondas 40 C A P Í T U L O 1 • Teoría del sonido http://es.wikipedia.org/wiki/Octava http://www.analog.com/en/content/0,2886,761%255F795%255F91588%255F0,00.html Lista de figuras obtenidas de Internet Figura 1.10 Figura 1.28 http://www.virtins.com http://www.renoise.com/ 41 Parte II Programación C A P Í T U L O 2 • DirectX y la API DirectSound 2 DirectX y la API DirectSound 2.1 ¿Qué es DirectX? 2.1.1 Introducción En los inicios del desarrollo de aplicaciones multimedia en Windows era muy difícil poder escribir código eficiente que pudiera controlar con éxito los efectos de vídeo y audio en este sistema operativo. Aunque el objetivo de Windows era aislar al programador del hardware, lo cual era origen de numerosas dificultades de operatividad en sistemas MS-DOS, sin embargo este aislamiento no gustó al principio a los desarrolladores multimedia pues seguían con el hábito de trabajar directamente con el hardware para poder así sacar el máximo rendimiento a las aplicaciones. Paulatinamente, fueron aceptándose las dificultades que implicaba programar aplicaciones en MS-DOS: falta de uniformidad en la plataforma, imposibilidad para manejar una gama amplia de dispositivos hardware de entrada/salida multimedia, dificultad para acceder al conocimiento específico de cada arquitectura, hardware y recursos software, sistema operativo mono-usurario y mono-tarea, etc. DirectX ha permitido abrir la puerta a las funciones de Windows (sin pedir al programador que sacrifique el rendimiento) y poder abstraer de esta manera la complejidad del acceso al hardware. El objetivo de los creadores de DirectX era hacer la plataforma Windows un entorno atractivo para programar aplicaciones multimedia. Así, desde la aparición de esta tecnología han surgido juegos con movimientos suaves, animaciones cristalinas y sonido de alta calidad. Los desarrolladores consiguieron una nueva plataforma muy rica, de gran rendimiento y con gran operatividad; y los fabricantes de hardware dispondrían de un mercado más amplio para sus productos. Se había conseguido al fin la independencia del dispositivo que facilita el desarrollo de aplicaciones multimedia a gran escala. DirectX consta de unas librerías en código binario que abstraen las funciones de acceso al hardware y al mismo tiempo consigue un alto rendimiento. Se distribuye como un programa que se instala junto a las librerías del sistema y se proporciona adicionalmente y dirigido a profesionales del sector del desarrollo multimedia un SDK (Software Development Kit) que incluye las librerías estáticas y código fuente para poder crear aplicaciones DirectX. La versión actual de DirectX es la 11 que está disponible solamente para Windows Vista y Windows 7. 45 C A P Í T U L O 2 • DirectX y la API DirectSound 2.1.2 Filosofía de DirectX Los diseñadores de DirectX se plantearon los siguientes objetivos: • • • Rapidez Disminuir la latencia Ser humilde Rapidez: Este es el objetivo más importante de todos. En primer lugar DirectX debe ser muy rápido y por lo tanto se debe utilizar la asistencia hardware en todo momento y sólo en casos de su ausencia utilizar la emulación por software. Disminuir la latencia: El sistema operativo Windows permite la abstracción de casi todas las funciones. Normalmente, dicha abstracción representa un beneficio que libera al creador de software de los detalles específicos del entorno sobre el que se ejecuta la aplicación. Pero también, puede implicar un retardo al solicitar un servicio. Este retardo se le denomina latencia y debe ser disminuida al máximo. Ser humilde: Al desarrollar un librería software se intenta transmitir una idea personal de cómo hacer las cosas, no obstante, los estándares pueden facilitar las cosas considerablemente. El objetivo del diseño de DirectX es que fuera ampliamente aceptado y no irrumpir en las partes más importantes del diseño de la aplicación o en el proceso de ingeniería del software. 2.1.3 Arquitectura de DirectX La arquitectura está basada en un “equipo de controladores”: dos controladores, la capa de abstracción del hardware (HAL) y la capa de emulación de hardware (HEL) colaboran para ejecutar las peticiones de servicio realizadas por DirectX. Cuando se crea un objeto DirectX para un determinado dispositivo, DirectX consulta el hardware para una determinada posibilidad (por ejemplo, un coprocesador gráfico que puede realizar operaciones de escalado) y éste utilizará el hardware que proporcione dicha función. Cuando no haya asistencia de hardware se recurrirá a la capa HEL. En la figura 2.1 puede verse dicha distribución: DirectX HAL HEL Hardware Figura 2.1 Arquitectura HAL/HEL de DirectX 46 C A P Í T U L O 2 • DirectX y la API DirectSound 2.1.4 Componentes de DirectX Los diferentes componentes de DirectX se ofrecen al programador a través de diferentes APIs (Application program interface). La versión 9 de DirectX (utilizada en este estudio) consta de los siguientes componentes: • DirectDraw: permite la animación suave de sprites y gráficos usando intercambio de páginas de vídeo, acceso a coprocesadores gráficos especializados (GPU’s) y administración de memoria de vídeo. Sirve de base para otros componentes como Direct3D y DirectShow. • Direct3D: proporciona funciones 3D en tiempo real utilizando el hardware de la GPU. • DirectSound: tratado en este capítulo. Proporciona sonido estéreo y 3D con mezcla de sonido por hardware así como administración de la memoria de la tarjeta de sonido. • DirectMusic: proporciona funciones de alto nivel para manejar funciones MIDI con baja latencia y una mayor abstracción de los objetos de sonido. Este componente utiliza las funciones de DirectSound. • DirectShow: facilita el desarrollo de aplicaciones de vídeo con uso de filtros y codecs de compresión de audio y vídeo. • DirectPlay: incluye servicios transparentes de mensajería independientes del medio para crear juegos en red local (LAN) e Internet, de forma que puedan cooperar diferentes jugadores. • DirectInput: proporciona entrada de baja latencia desde una amplia variedad de dispositivos de entrada y permite el funcionamiento de dispositivos de salida, incluyendo periféricos activos de juego (joysticks, volantes, etc…) . 2.1.5 Programación en DirectX Para programar en DirectX podemos utilizar cualquier lenguaje existente en plataforma Windows: Visual Basic, C# y Visual C++. En este estudio abordaremos el desarrollo de aplicaciones en DirectSound utilizando este último. Para el correcto funcionamiento de las aplicaciones en DirectX es fundamental conocer a fondo la API de Windows (API Win32), es decir, es necesario para el programador tener conocimientos consistentes sobre los entresijos de la programación en este sistema operativo comercial. En caso de que el lector no posea estos conocimientos, se le remite a la lectura del libro de Programación para Windows 95 de Charles Petzold. Es fundamental para poder programar con DirectX tener una ligera noción sobre la tecnología COM (Modelo de objetos componentes) ya que es la base de la filosofía DirectX y, como consecuencia, será explicado en el siguiente apartado. 47 C A P Í T U L O 2 • DirectX y la API DirectSound 2.1.6 Tecnología COM La tecnología COM es ampliamente utilizada en Windows, de hecho, es un estándar creado por Microsoft, que junto con el middleware DCOM forma la base para la tecnología de objetos distribuidos. COM es un modelo binario que puede ser utilizado independientemente de los lenguajes de programación que puedan soportarlo. CORBA y Java RMI son otros de los modelos que compiten con COM. Un componente es un objeto que incluye unas características mínimas, para que puedan ser utilizados tanto en un programa como en la etapa de diseño del mismo. Los componentes COM pueden utilizarse en múltiples lenguajes, de ahí que estén más extendidos. Además, las distintas partes del propio sistema operativo están realizadas con COM. La dificultad de esta tecnología estriba en la complejidad para su desarrollo. El origen de COM está en Microsoft, en sus primeros sistemas operativos, pues encontraron el problema de insertar gráficos de unas aplicaciones en otras. En 1991 desarrollaron un protocolo mediante el cual en un documento podrían insertarse objetos mantenidos por programas distintos en los que se estaba editando. El protocolo se llamaba OLE 1.0 y se basaba en el paso de mensajes y el uso de memoria global compartida. El resultado fue realmente malo, no sólo por la fragilidad del sistema (con la caída de una de las aplicaciones caía el sistema), sino por la complejidad de la realización de componentes para los programadores. La siguiente versión de OLE (llamada COM) se rescribió desde cero por Microsoft y DEC en un principio. Al final DEC abandonó el proyecto y Microsoft continuó con él. Las nuevas versiones que realizó Microsoft ampliaron el modelo para poder usar los componentes desde distintos ordenadores ( DCOM o Distributed COM ). El objetivo de la tecnología COM es utilizar la programación orientada a objetos que posteriormente se encontrarán a nivel de programas binarios y que por lo tanto, para poder usarlos no es necesario tener su código fuente, lo que fomenta la transparencia y la abstracción. Se pueden utilizar las librerías de enlace dinámico o DLL’s para albergar estos componentes. La característica de un objeto COM es que puede ejecutarse fuera del ámbito de un proceso, es decir, puede estar en ejecución en otros procesos de la misma máquina o de otra máquina diferente. El modelo COM sigue la arquitectura cliente/servidor. El objeto COM en sí es el servidor, y es usado por un programa que hace de cliente. Existen varias formas de realizar la comunicación. Podemos encontrar un componente COM como parte de un ejecutable, o en una DLL o incluso en otra máquina (DCOM). Los servidores COM pueden ser escritos en variedad de lenguajes y en sistemas operativos distintos, mientras que, por ejemplo, los objetos escritos en C++ son siempre para C++. Es normal que un servidor tenga varios objetos COM, aunque se presenta un problema a la hora de identificar qué componente se quiere usar cuando reside en un lugar distinto al cliente. La forma de conseguir la unicidad del objeto COM es con una secuencia de 128 bits que lo identifique unívocamente y que la probabilidad de conflicto sea prácticamente nula. Esta secuencia se llama GUID (Identificador único global). Los GUIDs utilizados para identificar objetos COM se denominan CLSID (Identificador de clase). 48 C A P Í T U L O 2 • DirectX y la API DirectSound La programación en COM está básicamente orientada a objetos. Pero en este punto es necesario definir la noción de Interfaz. Así, para COM una interfaz es un conjunto de declaraciones de funciones que puede o no implementar un objeto. La definición de Interfaz es: conjunto de funciones que se ponen a disposición del público. Suelen tener relación entre sí. Las interfaces también tienen un GUID (denominado IID, o identificador de interfaz ). Un mismo objeto implementa normalmente varias interfaces. En la siguiente figura se puede observar un ejemplo de objeto COM. CLSID_IObjeto1 Objeto1 Interfaz1 Interfaz2 - metodo1 - metodo1 - metodo2 - metodo2 - metodo3 - metodo3 IID_IInterfaz1 IID_IInterfaz2 Figura 2.2 Objeto COM Uno de los componentes principales de modelo COM son las interfaces, que, aunque este concepto no existe en programación C++, sí existe en otros lenguajes como Java. Las interfaces en C++ se implementan como clases abstractas. Toda interfaz en COM deriva de la interfaz IUnknown. Por lo tanto, una interfaz es una clase que contiene un conjunto de punteros a funciones que se encuentran en la tabla de métodos virtuales VMT que genera el compilador. El cliente de un objeto COM no accederá directamente al objeto, sino que lo hará a través de sus interfaces. Lo único que poseerá el cliente será un puntero a la interfaz que implementará los métodos que llevan a cabo la lógica de negocio. Interfaz IUnknown: Como se ha mencionado anteriormente todas las interfaces derivan de IUnknown. Si nos fijamos en el gráfico 2.3, podemos observar que para acceder al resto de las interfaces se entra por la interfaz de arriba: 49 C A P Í T U L O 2 • DirectX y la API DirectSound IUnknown Interfaz 2 Objeto COM Interfaz 3 Figura 2.3 Se utiliza un lenguaje independiente de otros lenguajes de programación y de la plataforma llamado IDL (Interface Definition Language) para definir las interfaces y sus argumentos. A partir de este lenguaje sería posible obtener las declaraciones en distintos lenguajes de programación para que puedan ser usadas. Por consiguiente, la interfaz IUnknown quedaría representada en IDL de la siguiente manera: [ local, object huid(00000000-0000-0000-C000-000000000046) pointer_default(unique) ] interface IUnknown { HRESULT QueryInterface ( [in]REFIID riid, [out,iid_is(riid)] void **ppvObject); ULONG AddRef(); ULONG Release(); } En la definición de la intefaz IUnknown podemos apreciar tres métodos especiales: 1. QueryInterface(): Este método se utiliza para la invocación de interfaces, es decir, el objeto COM se puede ver como una caja negra que encapsula un conjunto de interfaces y métodos. Para acceder a la implementación de una determinada interfaz recurriremos a la utilización de este método, que nos devolverá un puntero a dicha interfaz. Su funcionamiento es siempre similar: el primero es el IID que el identificador de interfaz que se quiere invocar, y el segundo parámetro un doble puntero void a esa interfaz. Por ejemplo: 50 C A P Í T U L O 2 • DirectX y la API DirectSound CoCreateInstante(CLSID_Objeto1, NULL, CLSCTX_ALL, IID_IUnknown, (void **)&Interfaz1); Interfaz1->QueryInterface(IID_Interfaz2, (void **) &Interfaz2); 2. AddRef() y Release(): Permiten controlar el ciclo de vida del objeto COM, incrementando o disminuyendo el contador de referencias a dicho objeto, de forma que cuando el contador de referencias llegue a cero se liberará el objeto de la memoria. 51 C A P Í T U L O 2 • DirectX y la API DirectSound 2.2 Programación en DirectSound1 2.2.1 Introducción DirectSound proporciona las dos características requeridas por los desarrolladores de aplicaciones de sonido profesionales: velocidad y control. Entre sus características principales destacan: • • • • • • Uso automático de aceleración por hardware cuando se encuentra disponible. Mezcla ilimitada de sonidos. Reproducción de baja latencia. Efectos de sonido con posicionamiento 3D fácilmente integrables en aplicaciones Direct3D u OpenGL. Conversión automática de los formatos de onda de entrada (incluso de varios formatos) para que concuerden con el formato de salida. Acceso a las funcionalidades de la tarjeta de sonido sin acceder al hardware, a través de la API. 2.2.2 Arquitectura de DirectSound La estructura de base de la arquitectura DirectSound reside en la utilización de buffers secundarios de sonido, que representan un único sonido, ya sea estático (sonidos cortos fácilmente almacenables en bloques de memoria) o canalizados (los datos de audio se transfieren en bloques al dispositivo de salida). Un aspecto fundamental en la programación en DirectSound es que el tipo de datos de forma de onda almacenados en los buffers será, en todo caso, formato PCM. Apoyándose en los buffers primarios, DirectSound mezcla los sonidos almacenados en sus respectivos buffers secundarios a alta velocidad. Además realiza otras operaciones adicionales como conversiones de formato (por ejemplo, cambios de frecuencia de muestreo de 44 a 22 kHz) y aplicación de efectos especiales, como posicionamiento 3D del sonido o efectos de Echo, Delay o Chorus. Finalmente, desde el buffer primario los datos se envían al dispositivo de salida. DirectSound coloca de manera automática tantos buffers como le es posible en la memoria del hardware siempre que buffers y funciones de mezcla estén disponibles por hardware. Si no, DirectSound mezcla por software los buffers que residen en la memoria y los canaliza hacia el mezclador por hardware junto con los sonidos procedentes de los buffers por hardware. El proceso se ilustra en la figura 2.4: 1 En este capítulo se utilizará el compilador Microsoft Visual C++ 2005. 52 C A P Í T U L O 2 • DirectX y la API DirectSound Buffers por software Mezclador por software Buffer primario Dispositivo Buffers por hardware (si disponibles) Mezclador por hardware (si disponible) Figura 2.4 Arquitectura DirectSound 2.2.3 Formato de sonido soportado por DirectSound El formato de sonido de una forma de onda en DirectSound debe tener las siguientes características: • • • • El número de canales (1 = mono, 2 = estéreo, etc.) La frecuencia de muestreo (en Hertzios). El formato depende de la frecuencia y del número de canales, por lo que para el sonido estéreo habrá el doble de muestras por cada segundo de sonido. Las muestras de sonido estéreo están intercaladas, yendo en primer lugar el canal izquierdo. Las frecuencias de muestreo que admite el hardware de los PC son 11.025, 22.050 y 22.100 Hz. La resolución de la muestra en número de bits: 8 ó 16. El tag de formato, que identifica cómo deben interpretarse los datos de las muestras. En DirectSound deben ajustarse al tag WAVE_FORMAT_PCM. La información del formato se contiene en WAVEFORMATEX, que tiene la siguiente composición: Campo WORD wFormatTag WORD nChannels DWORD nSamplesPerSec DWORD nAvgBytesPerSec WORD nBlockAlign una estructura de tipo Descripción Tipo de formato. Debe ser WAVE_FORMAT_PCM Número de canales 1(mono), 2 (estéreo) Frecuencia de muestreo(en hertzios): Normalmente 11.025, 22.050, 44.100 Velocidad media de transferencia (nSamplesPerSec * nBlockAlign) Bytes por muestra (nChannels * nBitsPerSample / 8) 53 C A P Í T U L O 2 • DirectX y la API DirectSound WORD wBitsPerSample WORD cbSize Número de bits por muestra (8 ó 16) Número de bytes de información adicional: para propósito del usuario. Siempre es 0 Tabla 2.1 2.2.4 Creación del objeto DirectSound Para la creación del objeto DirectSound podemos DirectSoundCreate que se describe en la siguiente tabla: Parámetro LPGUID lpGUID LPDIRECTSOUND *ppDS LPUNKNOWN pUnkOuter utilizar la función Descripción Puntero al GUID del dispositivo, o NULL para el dispositivo por defecto Dirección del puntero que se iniciará mediante la llamada Debe ser NULL Tabla 2.2 No obstante, antes de crear el objeto DirectSound se precisa iniciar la librería de COM mediante la llamada a la función Win32 CoInitialize(NULL): #include "stdafx.h" #include #include #include #include #include #include #include <windows.h> <mmsystem.h> <dsound.h> <SDKsound.h> <math.h> <string> <sstream> #include "directsound.h" int _tmain(int argc, _TCHAR* argv[]) { // Inicializa COM CoInitialize(NULL); IDirectSound8* pDS = NULL; HRESULT hr; const LPCWSTR tituloVentana = (LPCWSTR) L"Directsound - Capítulo 2"; SetConsoleTitle(tituloVentana); // Crea IDirectSound a partir del dispositivo primario if( FAILED( hr = DirectSoundCreate8( NULL, &pDS, NULL ) ) ) printf("Error al crear DirectSound (codigo numero=%d)\n",hr); 54 C A P Í T U L O 2 • DirectX y la API DirectSound En las primeras líneas se incluyen los ficheros cabecera básicos para poder acceder a las definiciones de las funciones y clases. Después del punto de entrada main(), iniciamos COM y establecemos un título para la ventana de consola de aplicación Win32. Finalmente se invoca una referencia a la interfaz COM IDirectSound8 con el dispositivo de sonido por defecto. Para manejar información sobre errores en DirectX se utiliza con frecuencia una variable del tipo HRESULT que contiene un código de estado con respecto a la llamada a la función COM, que puede luego verificarse con las macros FAILED (para detectar algún error en la llamada) y SUCCEEDED (la función devolvió S_OK y funcionó correctamente). 2.2.5 Establecer el nivel cooperativo Posteriormente a la creación del objeto DirectSound necesitamos establecer el nivel cooperativo mediante la llamada SetCooperativeLevel. Esta función tiene el efecto de asociar DirectSound a una ventana y determinar cómo su aplicación va a compartir con otros el dispositivo de sonido. El prototipo de la función es: HRESULT IDirectSound::SetCooperativeLevel(HWND hwnd, DWORD dwLevel) El primer parámetro es un handle de ventana y si todas las ventanas hijas de la aplicación derivan de la misma ventana principal, se debería pasar el handle de la ventana de nivel superior a dichos métodos. El segundo parámetro permite fijar el valor del nivel cooperativo como se muestra en la tabla 2.3 en orden ascendente de prioridad: dwLevel DDSCL_NORMAL Ventajas Mejor cooperación con otras aplicaciones. DDSCL_PRIORITY Permite modificar el formato primario. DDSCL_EXCLUSIVE Garantiza el uso exclusivo del dispositivo, por lo que las aplicaciones de fondo permanecen mudas. DDSCL_WRITEPRIMARY Garantiza el acceso al buffer primario para realizar una mezcla personalizada. Desventajas No se puede modificar el formato primario, por lo que se está restringiendo al formato de salida por defecto de DirectSound. Podría terminar modificando la salida de otra aplicación. Podría no ser apropiado silenciar todas las aplicaciones de fondo. Requiere un controlador de DirectSound; no se pueden reproducir buffers secundarios; las aplicaciones restantes pierden sus buffers. Tabla 2.3 55 C A P Í T U L O 2 • DirectX y la API DirectSound Casi todas las aplicaciones utilizan un nivel cooperativo DDSCL_NORMAL o DDSCL_PRIORITY. El nivel DDSCL_WRITEPRIMARY sólo es necesario para aplicaciones altamente especializadas que necesitan realizar sus propias operaciones de mezclado. En el fragmento de código del ejemplo se muestra el funcionamiento de esta función: // Establece el nivel cooperativo de DirectSound if (FAILED( hr = pDS>SetCooperativeLevel(FindWindow(NULL,tituloVentana),DSSCL_PRIORITY) ) ) printf("Error en la funcion SetCooperativeLevel (codigo numero %d)\n", hr ); 2.2.6 Crear el buffer primario Por defecto el buffer primario tiene una frecuencia de 22.050 Hz, 2 canales y 8 bits por muestra. En el buffer primario es donde se mezclan todos los sonidos, con independencia de sus formatos antes de reproducirse. En el ejemplo se ha establecido un formato primario de 16 bits, estéreo y 44.100 Hz; obviamente, DirectSound canalizará los datos al buffer primario con más rapidez si no tiene que convertirlos. Es significativo advertir que DirectSound está optimizado para trabajar con muestras de 16 bits, y no se produce pérdida alguna de rendimiento por cambiar el formato del buffer primario a uno de 16 bits. En el código del ejemplo siguiente se muestra cómo obtener un buffer primario: // Crea el buffer primario LPDIRECTSOUNDBUFFER pDSBPrimary = NULL; // Obtiene el buffer primario DSBUFFERDESC dsbd; ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) ); dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER ; dsbd.dwBufferBytes = 0; dsbd.lpwfxFormat = NULL; if( FAILED( hr = pDS->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL ) ) ) printf("Error al crear el buffer primario (codigo error = %d)", hr ); // Establece el formato del buffer primario WAVEFORMATEX wfx; ZeroMemory( &wfx, sizeof(WAVEFORMATEX) ); wfx.wFormatTag = (WORD) WAVE_FORMAT_PCM; wfx.nChannels = 2; wfx.nSamplesPerSec = 44100; wfx.wBitsPerSample = 16; wfx.nBlockAlign = (WORD) (wfx.wBitsPerSample / 8 * wfx.nChannels); wfx.nAvgBytesPerSec = (DWORD) (wfx.nSamplesPerSec * wfx.nBlockAlign); 56 C A P Í T U L O 2 • DirectX y la API DirectSound if( FAILED( hr = pDSBPrimary->SetFormat(&wfx) ) ) printf("Error al poner formato al buffer primario (codigo error =%d)", hr ); 2.2.7 Introducción a los buffers secundarios El objeto básico del sonido es el buffer y se representa mediante la interfaz IDirectSoundBuffer. Los buffers secundarios son circulares. Si un buffer se está reproduciendo de manera continua, tan pronto como DirectSound llega al final vuelve de nuevo al principio. Si se están canalizando datos hacia el buffer, es responsabilidad del programador enlazar los datos de esta forma. DirectSound mantiene un par de punteros por cada buffer de sonido: la posición actual de reproducción y la posición actual de escritura. La más necesaria es la posición actual de reproducción, o cursor de reproducción, que es el desplazamiento del siguiente byte de datos que se va a enviar al mezclador. Respecto a la posición actual de escritura, DirectSound no está escribiendo en dicha posición y, en muchos casos, ni siquiera lo hace la aplicación. Lo que en realidad representa la posición de escritura es el primer punto del buffer, más allá de la posición de reproducción, en el que es seguro escribir. Los diferentes tipos de buffers secundarios se dividen en estáticos y canalizados. Un buffer secundario estático es aquel que se usará para un único sonido corto que se emplee con frecuencia. Un buffer canalizado está destinado a un sonido que no cabría en un buffer de un tamaño razonable y del que se debe copiar una parte determinada a medida que se reproduce. La única diferencia entre estos dos tipos de buffers estriba en la optimización. Se pueden canalizar datos hacia un buffer estático o usar un buffer canalizado para un sonido estático, pero ninguno de ellos funcionaría a la perfección. Por lo tanto, mediante el uso de las constantes de la API: DSBCAPS_STATIC, DSBCAPS_LOCHARDWARE y DSBCASP_LOCSOFTWARE se pueden definir las diferentes combinaciones de tipos de buffers. 2.2.8 Pasos para crear los buffers secundarios Los pasos implicados en la creación de buffers secundarios son los siguientes: 1. Iniciar una estructura del tipo WAVEFORMATEX que describa el formato del sonido. 2. Iniciar una estructura del tipo DSBUFFERDESC con los parámetros del buffer, incluyendo un puntero a su estructura WAVEFORMATEX. 3. Llamar al método CreateSoundBuffer para crear el buffer. 4. Bloquear el buffer, entero o sólo una parte. 5. Copiar datos al buffer(normalmente provenientes de un fichero de audio o generados con una función matemática). 6. Desbloquear el buffer. 7. Fijar la posición de reproducción. 8. Reproducir el buffer. 9. Si se trata de un buffer canalizado, repetir los pasos 4, 5 y 6. 57 C A P Í T U L O 2 • DirectX y la API DirectSound 2.2.9 Archivos de sonido Para trabajar con sonidos en DirectSound es necesario recurrir a alguna fuente de sonido almacenado en formato PCM en memoria secundaria. Este es el caso de los ficheros WAV (originarios de Microsoft) y muy utilizados en Windows. Estos archivos están en formato RIFF (Resource Interchange File Format), formato que almacena cabeceras y datos en bloques de longitud variable que siempre comienzan con un código de cuatro letras, por ejemplo: “wave” o “data”. Los archivos de onda incluyen un bloque “fmt” que se corresponde con una estructura del tipo WAVEFORMATEX. El bloque “data”, dando por su puesto que el formato es PCM, es tan sólo una colección de muestra de 8 ó 16 bits. En el ejemplo que se incluye con el estudio se ha recurrido a la clase CWaveFile definida en el fichero del SDK de DirectX como SDKSound.cpp y SDKSound.h que deben de incluirse en el proyecto para poder trabajar con ficheros WAV. 2.2.10 Describir el buffer Para describir el buffer se utiliza la estructura DSBUFFERDESC que inicializaremos con algunos de los siguientes flags: Flag DBSCAPS_CTRL3D Notas Se utiliza para posicionamiento en 3D del sonido, si y sólo si se crea una interfaz IDirectSound3DBuffer. No puede combinarse con DSBCAPS_CTRLPAN DSBCAPS_CTRLALL Establece todos los controles DSBCAPS_CTRLDEFAULT Establece controles por defecto DSBCAPS_CTRLFREQUENCY Cambia la frecuencia de reproducción DSBCAPS_CTRLPAN Cambia el pan (balance) DSBCAPS_POSITIONNOTIFY Informa o notifica sobre el posicionamiento DSBCAPS_CTRLVOLUME El buffer va a sufrir cambios de volumen DSBCAPS_LOCHARDWARE Buffer canalizado por hardware DSBCAPS_LOCSOFTWARE Buffer canalizado por software DSBCAPS_STATIC Buffer estático DSBCAPS_PRIMARYBUFFER El buffer es primario DSBCAPS_MUTE3DATMAXDISTANCE Hace que los buffers 3D sean más eficientes reduciendo el número de cálculos necesarios. Tabla 2.4 58 C A P Í T U L O 2 • DirectX y la API DirectSound 2.2.11 Creación del buffer secundario Para crear el buffer secundario utilizaremos el método: HRESULT IDirectSound::CreateSoundBuffer Con los siguientes parámetros: Parámetro LPCDBUFFERDESC lpcDSBufferDesc LPLDIRECTSOUNDBUFFER lplpDirectSoundBuffer IUnknown FAR *pUnkOuter Descripción Puntero a la estructura de descripción del buffer. Recibe el puntero a la interfaz Debe ser NULL Tabla 2.5 A continuación se muestra el código que lo hace posible: / Crea un buffer secundario con sonido LPDIRECTSOUNDBUFFER* LPDIRECTSOUND3DBUFFER* DWORD CWaveFile* const int apDSBuffer apDS3DBuffer dwDSBufferSize pWaveFile NUM_BUFFERS = = = = = NULL; NULL; NULL; NULL; 3; // Arrays de buffers secundarios y 3D respectivamente apDSBuffer = new LPDIRECTSOUNDBUFFER[NUM_BUFFERS]; apDS3DBuffer = new LPDIRECTSOUND3DBUFFER[NUM_BUFFERS]; if( apDSBuffer == NULL ) printf("Falta memoria para buffers secundarios"); if( apDS3DBuffer == NULL ) printf("Falta memoria para buffers 3D"); for(int i = 0;i<NUM_BUFFERS; i++ ) { pWaveFile = new CWaveFile(); if( pWaveFile == NULL ) printf("Falta memoria para objeto CWaveFile"); std::wstring fichero,indice; std::wostringstream iss; iss << i+1; fichero = L"..\\media\\sonido" + iss.str() + L".wav"; // Abre fichero WAV/PCM para leer cabecera pWaveFile->Open((LPWSTR)fichero.c_str(), NULL, WAVEFILE_READ ); if( pWaveFile->GetSize() == 0 ) // Fichero inexistente o vacío printf("Error al leer fichero WAV"); 59 C A P Í T U L O 2 • DirectX y la API DirectSound // El buffer secundario debe tener el mismo tamaño que el fichero WAV dwDSBufferSize = pWaveFile->GetSize(); // Crea el buffer secundario con las propiedades deseadas ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) ); dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = DSBCAPS_STATIC | DSBCAPS_CTRLFREQUENCY |DSBCAPS_CTRLVOLUME | DSBCAPS_CTRL3D; dsbd.dwBufferBytes = dwDSBufferSize; dsbd.guid3DAlgorithm = GUID_NULL; dsbd.lpwfxFormat = pWaveFile->m_pwfx; // Crea el buffer secundario DirectSound if (FAILED(hr = pDS->CreateSoundBuffer( &dsbd, &apDSBuffer[i], NULL ))) printf("Error al crear buffer secundario (codigo error =%d)", hr ); 2.2.12 Sonido en tres dimensiones Una de las posibilidades de DirectSound es el posicionamiento 3D en tiempo real del sonido. Éste coloca los sonidos en el espacio siguiendo el mismo sistema de coordenadas que usa Direct3D y OpenGL. Para poder adaptar una aplicación DirectSound a DirectSound 3D es tan sencillo como añadir las dos siguientes interfaces: • • IDirectSound3DBuffer: controla las propiedades 3D de fuentes de sonido individuales, es decir, buffer secundarios. IDirectSound3DListener: se emplea para colocar un oyente virtual y para manipular las propiedades generales del entorno del sonido 3D. DirectSound puede proporcionar el efecto sonoro de la posición y el movimiento de una fuente acústica de las siguientes formas: • • • • • Atenuando la amplitud dependiendo de la distancia que haya desde la fuente del sonido hasta el oyente. Atenuando la amplitud en uno u otro lado, o realizando un desplazamiento, para indicar la orientación hacia la izquierda o la derecha de la fuente de sonido. Truncando un sonido que se encuentre justo delante del oyente, de acuerdo con la orientación de sus oídos y el efecto de bloqueo de sonido de su cabeza. Aplicando un breve retardo a la reproducción del sonido en un lado o en otro para reflejar la mayor distancia recorrida por las ondas sonoras originadas en el lado opuesto de la cabeza. (A este efecto se le denomina ITD, lo que significa retardo “interaural” o diferencia de tiempo “interaural”). Incrementando o disminuyendo la frecuencia de una fuente de sonido para crear un efecto Doppler, simulando el efecto de fuentes sonoras que se mueven. Las coordenadas que utiliza DirectSound 3D son las cartesianas X, Y y Z. Las posiciones se miden desde el origen de coordenadas (0.0, 0.0, 0.0). Para representar las coordenadas 3D se utiliza el tipo de datos D3DVALUE. En Direct3D, una diferencia de 60 C A P Í T U L O 2 • DirectX y la API DirectSound 1.0 entre dos puntos situados a lo largo de un vector no se corresponde con una determinada distancia del mundo real; es responsabilidad del diseñador establecer una escala. Por defecto, 1.0 equivale a 1 metro. Por lo tanto, un buffer con una posición (2.0, 0.0, 8.0) estará situado un metro a la izquierda y 8 metros por delante de la posición por defecto del oyente. Para obtener una interfaz IDirectSound3D, es necesario invocarla a través de la interfaz IDirectSoundBuffer con el método COM QueryInterface. De esta forma quedaría el código: // Obtiene interfaces 3D if (FAILED(hr = apDSBuffer[i]->QueryInterface( IID_IDirectSound3DBuffer, (VOID**) &apDS3DBuffer[i]))) printf("Error al obtener buffer 3D (codigo error =%d)", hr ); y obtiene tres buffers 3D. A partir de aquí es necesario fijar las propiedades del buffer 3D; para ello se utiliza la estructura DS3DBUFFER donde se asignarán los flags que describen las características del buffer. En esta estructura se puede definir cuándo un sonido debe situarse en el espacio de forma relativa al oyente o de manera absoluta, dependiendo del modo del buffer. Los siguientes flags representan los tres modelos mediante los que se puede configurar un buffer 3D: • • • DS3DMODE_HEARDRELATIVE: el buffer se coloca en una posición relativa a la del oyente. Es el modo que se usará en el ejemplo. DS3DMODE_NORMAL: el buffer se coloca de forma relativa al espacio y no se desplaza a medida que lo hace el oyente. La mayoría de los sonidos 3D funcionan con este método; ésta es la configuración por defecto. DS3DMODE_DISABLE: esta configuración se puede utilizar para ahorrar tiempo de CPU al desactivar el proceso 3D de un buffer de sonido. // Propiedades del buffer 3D DS3DBUFFER ds3db; ZeroMemory(&ds3db,sizeof(DS3DBUFFER)); ds3db.dwSize = sizeof(DS3DBUFFER); apDS3DBuffer[i]->GetAllParameters( &ds3db); ds3db.dwMode = DS3DMODE_HEADRELATIVE; apDS3DBuffer[i]->SetAllParameters(&ds3db, DS3D_IMMEDIATE ); 2.2.13 Bloqueo del buffer Ya se ha visto cómo se crea el buffer secundario y se establecen sus propiedades. A continuación se explicará cómo bloquearlo antes de desplazar datos desde el archivo de sonido al buffer. De esta forma nos aseguramos de que el buffer permanece en una situación estable y al mismo tiempo, se obtiene su dirección de memoria. En la tabla 2.6 se muestran los parámetros del método Lock. 61 C A P Í T U L O 2 • DirectX y la API DirectSound Parámetro DWORD dwWriteCursor DWORD dwWriteBytes DWORD lplpvAudioPtr1 LPDWORD lpdwAudioBytes1 LPVOID lplpAudioPtr2 LPDWORD lpdwAudioBytes2 DWORD dwFlags Descripción Desplazamiento a la zona en la que comenzará la parte bloqueada. Número de bytes a bloquear. Recibe la dirección de inicio de bloqueo. Recibe el tamaño de bloqueo, o la primera porción si el bloqueo es circular. Recibe la dirección de la segunda porción del bloqueo; si no lo es, recibe NULL. Recibe el número de bytes de la segunda porción del bloqueo si éste es circular; si no lo es, recibe 0. DSBLOCK_FROMWRITECURSOR o DSBLOCK_ENTIREBUFFER Tabla 2.6 En el fragmento de código siguiente se muestra el ejemplo del bloqueo del buffer antes de la reproducción. VOID* pDSLockedBuffer = NULL; // Puntero a la memoria bloqueada DWORD dwDSLockedBufferSize = 0; // Tamaño de la zona bloqueada DWORD dwWavDataRead = 0; // Cantidad de datos del fichero WAV a leer // Bloquea el buffer if( FAILED( hr = apDSBuffer[i]->Lock( 0,dwDSBufferSize, &pDSLockedBuffer,&dwDSLockedBufferSize, NULL, NULL, 0L ) ) ) printf("Error de bloqueo del buffer (codigo error=%d)", hr ); // Empieza a leer desde el inicio del fichero pWaveFile->ResetFile(); if( FAILED( hr = pWaveFile->Read( (BYTE*) pDSLockedBuffer, dwDSLockedBufferSize,&dwWavDataRead ) ) ) printf("Error leyendo el fichero de audio (codigo error= %d)", hr ); Por último, es necesario desbloquear el buffer. En la tabla 2.7 se muestran los parámetros del método Unlock. Parámetro LPVOID lpvAudioPtr1 DWORD dwAudioBytes1 LPVOID lpvAudioPtr2 DWORD dwAudioBytes2 62 Descripción Dirección de inicio del bloqueo. Número de bytes en la primera porción del bloqueo. Dirección de inicio del buffer si se produjo desbordamiento del buffer, si no es así, NULL. Número de bytes en la parte del buffer que C A P Í T U L O 2 • DirectX y la API DirectSound produjo el desbordamiento. Tabla 2.7 if ( FAILED( apDSBuffer[i]->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 ) ) ) printf("Error al desbloquear buffer (codigo error =%d)", hr ); SAFE_DELETE( pWaveFile ) // Libera el objeto CWaveFile } 2.2.14 Reproducir y posicionar el buffer en 3D Reproducir el buffer consiste en fijar la posición actual de reproducción al principio del buffer y llamar al método Play. En la tabla 2.8 se muestran sus parámetros: HRESULT IDirectSoundBuffer::Play Parámetros DWORD dwReserved1 DWORD dwReserved2 DWORD dwFlags Descripción Debe ser 0. Debe ser 0. 0 o DBSPLAY_LOOPING. Tabla 2.8 case '1': if (FAILED (hr = apDSBuffer[0]->Play( 0, 0, 0))) printf("Error al reproducir buffer 1 (codigo error =%d)", hr ); break; Es indispensable controlar la gestión de una pérdida del buffer. Si el error que devuelve Play es DSERR_BUFFERLOST es porque un buffer mediante hardware puede perder su espacio de memoria debido a que otra aplicación tome el control de dicho buffer. Cuando esto sucede todas las llamadas a Lock o Play que se realicen sobre dicho buffer generarán un error hasta que la aplicación recupere el espacio de memoria del buffer mediante una llamada al método Restore. Por último, si deseamos posicionar el buffer en 3D es necesario llamar al método: IDirectSoundBuffer3D::SetPosition al cual se debe pasar la posición 3D en formato D3DVALUE de las coordenadas x,y,z y una constante que debe ser: • • DS3D_DEFERRED: Los parámetros no se hacen efectivos hasta que no se llama al método: IDirectSound3DListener8::CommitDeferredSettings. DS3D_INMEDIATE: Los parámetros son aplicados inmediatamente. En el siguiente ejemplo podemos ver una aplicación de posicionamiento 3D: 63 C A P Í T U L O 2 • DirectX y la API DirectSound case '4': for( double alfa=0.0; alfa < circulo ; alfa += 0.1 ) { D3DVALUE x = D3DVALUE(distancia*cos(alfa)); D3DVALUE y = D3DVALUE(distancia*sin(alfa)); if ( FAILED( hr = apDS3DBuffer[0]>SetPosition(x,y,0,DS3D_IMMEDIATE) ) ) printf("Error al posicionar 3D (codigo error =%d)", hr ); Sleep(100); } break; 2.2.15 Liberar las interfaces En último lugar, una vez acabado el procesamiento de sonido y con vistas a salir de la aplicación es necesario liberar las referencias a las interfaces COM. Para ello se debe llamar a la macro definida en directsound.h como SAFE_RELEASE. En el siguiente fragmento de código podemos ver su utilización: // Libera interfaces y arrays SAFE_RELEASE(pDSBPrimary); SAFE_RELEASE(apDSBuffer[0]); SAFE_RELEASE(apDSBuffer[1]); SAFE_RELEASE(apDSBuffer[2]); SAFE_RELEASE(apDS3DBuffer[0]); SAFE_RELEASE(apDS3DBuffer[1]); SAFE_RELEASE(apDS3DBuffer[2]); SAFE_RELEASE(pDS); SAFE_DELETE(apDSBuffer); SAFE_DELETE(apDS3DBuffer); 64 C A P Í T U L O 2 • DirectX y la API DirectSound Bibliografía y referencias Libros A fondo DirectX – Bradley Bargen, Peter Donnelly. Microsoft Press, 1998. Programación COM y COM+ – Alan Gordon . Anaya Multimedia, 2001. Sistemas Distribuidos: conceptos y diseño – Colouris, Dolimore, Kindberg. Pearson Addison Wesley, 2001 La Web MSDN (Microsoft Developer Network): http://msdn2.microsoft.com/es-es/default.aspx Francisco Ángel Gimeno Doménech – Andrómeda Studios http://usuarios.multimania.es/andromeda_studios/paginas/tutoriales/articulo02.htm 65 C A P Í T U L O 3 • OpenAL 3 OpenAL 3.1 Introducción a OpenAL OpenAL son las siglas de Open Audio Library, una librería de código fuente abierto con una API multiplataforma. El diseño fue pensado para un alto rendimiento en el posicionamiento 3D multi-canal y se distribuye como librería adicional que se instala en los directorios del sistema. El estilo de OpenAL es muy parecido al de su semejante OpenGL, por lo que existen diversas analogías en la convención de llamadas a las funciones. La historia de OpenAL se remonta a la compañía de software Loki cuando intentaban poder realizar de una forma fácil la traslación de código de juegos programados en Windows sobre Linux. El proyecto fue distribuido con licencia pública como software libre y mantenido por esta comunidad. Más tarde fue la compañía de Creative Labs la que continuó el mantenimiento del proyecto, con apoyo de Apple y la comunidad de software libre. Figura 3.1 Logotipo de OpenAL 3.2 Arquitectura de OpenAL La arquitectura de OpenAL se basa en la Arquitecture Review Board o ARB que es el modelo usado por OpenGL. La estructura básica consta de 3 elementos principales: objetos source, buffers y listeners. Un objeto source contiene un puntero a un buffer, la velocidad, la posición, la dirección y la intensidad del sonido. Un objeto listener contiene la posición, la velocidad, la dirección y la ganancia global aplicada a todos los sonidos. Así mismo, los buffers son los responsables de albergar formas de onda de audio con codificación PCM, con resolución de 8 ó 16 bits y formato monoaural o estéreo. El motor de renderizado lleva a cabo todos los cálculos necesarios para obtener la atenuación de distancia, efecto Doppler, etc. El resultado neto de una aplicación desarrollada con esta arquitectura es que el usuario final aprecia que el sonido es muy natural y que el posicionamiento 3D está perfectamente acompasado con el movimiento por un escenario virtual. Desde la 67 C A P Í T U L O 3 • OpenAL perspectiva del programador de aplicaciones requiere de muy poco tiempo de codificación y de mantenimiento para cumplir los requisitos. De la misma forma que OpenGL, OpenAL consta de dos secciones de la API: el núcleo basado en las llamadas a las funciones y la API ALC que es usada para manejar contextos de renderización, uso de recursos y bloqueo multiplataforma. Además, imitando a su predecesor, OpenAL incluye una librería de alto nivel para programadores, llamada ALUT, con convenciones de llamadas muy parecidas a la GLUT de OpenGL cuyo cometido es simplificar algunas tareas complicadas como es la lectura de un fichero WAV. Dispositivo nº1 Listener Contexto nº1 Source 1 Source 2 Source 3 Source 4 Buffer 1 Buffer 2 Buffer 3 Buffer 4 Figura 3.2 Arquitectura de OpenAL Con el objetivo de proveer funcionalidad de cara al futuro, OpenAL utiliza un mecanismo de extensiones por el cual los fabricantes pueden incluir sus propias extensiones en distribuciones de OpenAL, con el propósito de exponer funciones adicionales en su hardware propietario. Las extensiones siguen la base ARB (Arquitecture Review Board), asegurando que va a existir retrocompatibilidad con las extensiones anteriores. Las extensiones ARB pueden ser agregadas al núcleo de la API tras un cierto período de tiempo. Entre las plataformas en las que se encuentra implementado OpenAL destacan las siguientes: • • • • • 68 Mac OSX GNU/Linux BSD Solaris IRIX C A P Í T U L O 3 • OpenAL • • Windows PC XBox 360 3.3 Programación con OpenAL1 3.3.1 Inicialización El primer paso para inicializar OpenAL es abrir el dispositivo. Una vez abierto con éxito, procederemos abrir un contexto sobre dicho dispositivo. A partir de entonces es cuando los objetos fundamentales de OpenAL pueden ser manejados. La función para abrir el dispositivo es la que se muestra a continuación: alcOpenDevice Parámetros conts ALCchar *devicename Descripción Un string describiendo el dispositivo Tabla 3.1 Si el string que contiene el nombre del dispositivo es null, se obtendrá el dispositivo por defecto. En caso de que el dispositivo no pueda ser abierto o se produzca un error devolverá la constante literal null. Después de iniciar el dispositivo es necesario crear el contexto, y para esto llamamos a la función: alcCreateContext Parámetros ALCdevice *device ALCint *attrlist Descripción Puntero al dispositivo Atributos: ALC_FREQUENCY ALC_MONO_SOURCES ALC_REFRESH ALC_STEREO_SOURCES ALC_SYNC Tabla 3.2 La función devolverá un puntero al nuevo contexto o null si ocurre algún error. Una vez creado el contexto, procederemos a informar al sistema sobre cuál va a ser el contexto con el que se activará la aplicación, para lo que se requiere llamar a la siguiente función: 1 En este capítulo utilizaremos el compilador Microsoft Visual C++ .NET 2010 que es el más avanzado en Windows 64 bits hasta la fecha de publicación de este tratado. 69 C A P Í T U L O 3 • OpenAL alcMakeContextCurrent Parámetros ALContext *context Descripción Puntero al contexto Tabla 3.3 En caso de error, la función devolverá la constante booleana ALC_FALSE, y en caso de éxito devolverá ALC_TRUE. Con estas llamadas tan simples podemos activar un dispositivo y un contexto de renderización para reproducir buffers de sonidos y activar sources y listeners. Por último, dentro de la sección de inicialización, existe la posibilidad de indagar en OpenAL la presencia de extensiones hardware, mediante la llamada a la función: alIsExtensionPresent Parámetros ALCdevice *device const ALCchar *extname Descripción Puntero al dispositivo del cual interesa averiguar extensión Cadena indicando la extensión Tabla 3.4 A continuación se muestra el código del ejemplo que consigue dichas tareas: #include #include #include #include #include <conio.h> <windows.h> <math.h> <string> <sstream> #include "al.h" #include "alc.h" #include "AL/alut.h" const int NUM_BUFFERS = 4; const int NUM_SOURCES = 4; void _tmain(int argc, _TCHAR* argv[]) { ALCdevice* Device; ALCcontext* Context; ALenum error; // Inicialización Device = alcOpenDevice(NULL); // Selecciona el dispositivo por defecto if (Device) alcMakeContextCurrent(Context = alcCreateContext(Device,NULL)); // Comprueba que existe la extensión EAX 2.0 ALboolean bEAX = alIsExtensionPresent("EAX2.0"); if (bEAX) printf("EAX2.0 presente en el sistema...\n"); alGetError(); // Limpia el código de error 70 C A P Í T U L O 3 • OpenAL 3.3.2 Trabajar con buffers Depués de iniciar la aplicación es preciso generar el primer elemento básico de la arquitectura de OpenAL: el buffer. Con este objeto podremos almacenar formas de ondas provenientes de ficheros WAV y reproducirlas más tarde. La función que lleva a cabo este cometido es: alGenBuffers Parámetros Descripción ALsizei n Número de buffers a generar ALuint *buffers Puntero a un array de valores ALuint que almacenará el nombre de los nuevos buffers. Tabla 3.5 Una vez creado el buffer es posible almacenar en él datos en formato PCM provenientes de un fichero WAV. Para realizar esta operación se utiliza una función de alto nivel definida en la librería externa “alut.h” que permite leer ficheros en formato WAV. alutLoadWAVFile Parámetros ALbyte* strFile ALenum* format ALvoid *data ALsizei *size ALsizei *freq ALboolean *loop Descripción Nombre del fichero Formato del fichero Datos en PCM Tamaño del fichero Frecuencia de sampleo Bucle de reproducción del fichero Tabla 3.6 Por último, y una vez leídos los datos PCM en un array, se procederá a copiarlos dentro del objeto buffer con la siguiente función: alBufferData Parámetros ALuint buffer ALenum format const ALvoid *data ALsizei size ALsizei freq Descripción Nombre del buffer a llenar con datos Puede ser uno de los siguientes: AL_FORMAT_MONO8 AL_FORMAT_MONO16 AL_FORMAT_STEREO8 AL_FORMAT_STEREO16 Puntero a los datos de audio El tamaño de los datos en bytes Frecuencia de muestreo Tabla 3.7 71 C A P Í T U L O 3 • OpenAL Esta función también puede leer datos en formatos diferentes a PCM si se utilizan las extensiones apropiadas. Con todo esto, el código necesario para implementar la generación y carga de datos en el buffer es como se muestra en el siguiente ejemplo: // Genera los buffers ALuint buffer[NUM_BUFFERS]; alGenBuffers(NUM_BUFFERS, buffer); printf("Generando buffers...\n"); if ((error = alGetError()) != AL_NO_ERROR) { printf("alGenBuffers :", error); terminaAplicacion(); return; } // Carga los ficheros WAV ALsizei size,freq; ALenum format; ALvoid *data; ALboolean loop; printf("Leyendo ficheros de audio...\n"); for(int i = 0; i < NUM_BUFFERS; i++) { std::string fichero,indice; std::ostringstream iss; iss << i+1; fichero = "..\\media\\sonido" + iss.str() + ".wav"; alutLoadWAVFile((ALbyte*)fichero.c_str(), &format, &data, &size, &freq, &loop); if ((error = alGetError()) != AL_NO_ERROR) { printf("alutLoadWAVFile: %d", error); // Delete Buffers alDeleteBuffers(NUM_BUFFERS, buffer); terminaAplicacion(); return; } // Copia a los respectivos buffers los datos printf("Copiando a buffer...\n"); alBufferData(buffer[i],format,data,size,freq); if ((error = alGetError()) != AL_NO_ERROR) { printf("alBufferData buffer 0 : %d", error); alDeleteBuffers(NUM_BUFFERS, buffer); terminaAplicacion(); return; } // Descarga el fichero WAV alutUnloadWAV(format,data,size,freq); if ((error = alGetError()) != AL_NO_ERROR) { printf("alutUnloadWAV : %d", error); alDeleteBuffers(NUM_BUFFERS, buffer); terminaAplicacion(); return; } } 72 C A P Í T U L O 3 • OpenAL 3.3.3 Reproducir sonido Como es habitual en toda aplicación de sonido, debe exisitir algún objeto que controle la reproducción de audio. En OpenAL tenemos el objeto source que permite llevar a cabo esta operación: alGenSources Parámetros ALsizei n ALuint *sources Descripción Número de sources a generar Puntero a un array de valores ALuint con los nombres de los sources Tabla 3.8 Con el fin de reproducir el sonido debemos adjuntar los buffers, creados en la sección anterior, a los sources mediante la llamada a la siguiente función: alSourcei Parámetros ALuint source ALenum param ALint value Descripción Nombre del source al que hay que establecer los atributos Uno de los siguientes atributos: AL_SOURCE_RELATIVE AL_CONE_INNER_ANGLE AL_CONE_OUTER_ANGLE AL_LOOPING AL_BUFFER AL_SOURCE_STATE Valor a establecer Tabla 3.9 En el siguiente fragmento de código se ilustra la manera de llevarlo a cabo: // Genera las fuentes ALuint source[NUM_SOURCES]; alGenSources(NUM_SOURCES,source); if ((error = alGetError()) != AL_NO_ERROR) { printf("alGenSources 1 : %d", error); alDeleteSources(NUM_SOURCES, source); alDeleteBuffers(NUM_BUFFERS, buffer); terminaAplicacion(); return; } // Adjunta los buffers a las fuentes for(int i=0;i < NUM_SOURCES; i++) { alSourcei(source[i], AL_BUFFER, buffer[i]); if ((error = alGetError()) != AL_NO_ERROR) { printf("alSourcei AL_BUFFER 0 : %d", error); 73 C A P Í T U L O 3 • OpenAL alDeleteSources(NUM_SOURCES, source); alDeleteBuffers(NUM_BUFFERS, buffer); terminaAplicacion(); return; } } Ahora ya es posible reproducir un source mediante la función: alSourcePlay Parámetros Descripción El nombre de la fuente a reproducir Tabla 3.10 ALuint source while( condicion ) { switch( _getch() ) { // Reproduce buffer 1 case '1': alSourcePlay(source[0]); Con estas funciones adicionales es posible también: • Detener el sonido alSourceStop(source[0]); • Rebobinar alSourceRewind(source[0]); • o pausarlo… alSourcePause(source[0]); 3.3.4 Posicionamiento 3D Esta es una de las características más avanzadas de OpenAL, ya que permimte utilizar diferentes configuraciones para manipular el sonido en tres dimensiones. Uno de los elementos fundamentales para el posicionamiento 3D es el listener, que se podría asemejar a los oídos del sujeto que se encuentra sumergido en el espacio virtual. Normalmente, los listeners suelen establecerse en la posición de la cámara, de manera que sensación acústica concuerde con la percepción visual. Así mismo, es posible aplicar un gran número de parámetros sobre el listener para controlar la orientación, posición y la velocidad. Este objeto debe ser único en la aplicación, lógicamente, ya que el sonido procesado mediante el mismo es luego dirigido a los canales estéreo de los altavoces. 74 C A P Í T U L O 3 • OpenAL Respecto al posicionamiento 3D de un sonido en OpenAL es posible llevarlo a cabo llamando a la siguiente función: alSourcefv Parámetros ALuint source ALenum param ALfloat *values Descripción Source al que hay que establecer los atributos Atributo a establecer. Puede ser uno de los siguientes: AL_POSITION AL_VELOCITY AL_DIRECTION Un puntero a un array de valores a establecer Tabla 3.11 En este caso se utilizará un vector de 3 elementos para almacenar las componentes X,Y y Z de la posición, velocidad o dirección del sonido 3D, como se muestra en el ejemplo programado: ALfloat source4Pos[3]; // Almacena la posicion del buffer 4 Por último, para realizar el posiconamiento 3D del sonido utilizaremos el siguiente código: for(float alfa=0.0f; alfa < circulo ; alfa += 0.1f ) { ALfloat x = distancia*cos(alfa); ALfloat y = distancia*sin(alfa); source4Pos[0] = x; source4Pos[1] = y; source4Pos[2] = 0.0f; alSourcefv(source[3], AL_POSITION, source4Pos); if ((error = alGetError()) != AL_NO_ERROR) { printf("Error al posicionar 3D buffer4 :", error); alDeleteSources(NUM_SOURCES, source); alDeleteBuffers(NUM_BUFFERS, buffer); terminaAplicacion(); return; } Sleep(100); } alSourcei(source[3], AL_LOOPING, AL_FALSE); 75 C A P Í T U L O 3 • OpenAL Figura 3.3 Descripción del efecto de giro de 360º creado sobre el oyente en el ejemplo anterior 3.3.5 Salida de la aplicación Para terminar la aplicación de forma controlada necesitamos obtener un puntero al contexto actual, obtener el dispositivo activado dentro de él y posteriormente establecer el contexto actual como null, destruir el contexto y cerrar el dispositivo. A continuación se muestra la secuencia del código que lo realiza: // Termina la aplicación // Libera el contexto y el dispositivo void terminaAplicacion() { ALCcontext* Context = alcGetCurrentContext(); ALCdevice* Device = alcGetContextsDevice(Context); alcMakeContextCurrent(NULL); alcDestroyContext(Context); alcCloseDevice(Device); alutExit(); } También es importante liberar los buffers y los sources creados para manejar audio. Las funciones alDeleteBuffers y alDeleteSources son las responsables de destruir los objetos buffer y source que se hayan creado durante la ejecución de la aplicación. De esta manera se liberará el espacio de memoria utilizado para dicho propósito en el sistema. El siguiente fragmento de código ha sido utilizado en el ejemplo para liberar los objetos sources y buffers en el momento de romper el flujo de control para salir de la aplicación: alDeleteSources(NUM_SOURCES, source); alDeleteBuffers(NUM_BUFFERS, buffer); terminaAplicacion(); 76 C A P Í T U L O 3 • OpenAL Ya sólo faltaría, por último, salir de la aplicación OpenAL mediante a la llamada: aluExit() 3.3.6 Gestión de errores Cuando se produce una situación anómala en la llamada a cualquier función de OpenAL se genera un estado de error en la librería que informa con un código el tipo de error que se ha producido. Evidentemente, este tipo de gestión de errores se realiza mediante consulta utilizando la función: ALenum alGetError(ALvoid) La consiguiente llamada a la función producirá que se elimine el estado de error de la memoria. El uso de esta función puede verse en el siguiente ejemplo: ALuint buffer[NUM_BUFFERS]; alGenBuffers(NUM_BUFFERS, buffer); printf("Generando buffers...\n"); if ((error = alGetError()) != AL_NO_ERROR) { printf("alGenBuffers :", error); terminaAplicacion(); return; } 3.4 Ventajas de OpenAL La primera gran ventaja de OpenAL con respecto a otras librerías de audio como DirectSound es la portabilidad. Se puede utilizar en gran cantidad de plataformas, desde Windows hasta Linux, con lo que los desarrolladores tienen un gran abanico de sistemas que pueden cubrir utilizando el mismo código sin necesidad de cambiar nada. Las razones son estratégicas a la hora de abordar aplicaciones multiplataforma, cada vez más de moda y que repercute en aspectos tales como la Ingeniería de Software (reutilización de código, diseño, pruebas, mantenimiento, evaluación, etc.) así como económicas. La segunda gran ventaja es la simplicidad de la arquitectura y del código. No es necesario tecnología de programación compleja, como es el caso de COM en DirectX, no hay que complicar lo evidente con funciones adicionales como es el caso de la “pérdida del buffer” en DirectSound. La arquitectura es fácilmente comprensible: las entidades utilizadas son las básicas de cualquier aplicación de sonido. La últimas grandes ventajas son la eficiencia y las prestaciones. OpenAL proporciona una baja latencia y un rendimiento óptimo de CPU a la hora de mezclar varios buffers de sonido y realizar cálculos de posicionamiento 3D espacial con muchas fuentes de sonido. 77 C A P Í T U L O 3 • OpenAL Como conclusión se puede destacar que OpenAL se presenta como una gran alternativa a otras librerías de sonido por sus múltiples ventajas a la hora del desarrollo de software de audio. Bibliografía y referencias referencias Manuales OpenAL Programmer's Guide - OpenAL Versions 1.0 and 1.1 La Web Wikipedia: http://en.wikipedia.org/wiki/OpenAL 78 Parte III MIDI C A P Í T U L O 4 • Introducción a MIDI 4 Introducción a MIDI 4.1 ¿Qué es MIDI? 4.1.1 Un poco de historia Al comienzo del desarrollo de la tecnología musical, todos los sintetizadores eran monofónicos, es decir, sólo eran capaces de reproducir una sola nota al mismo tiempo. A finales de la década de los 70 hizo su aparición el sintetizador digital y trajo consigo el problema de la incompatibilidad entre sistemas de fabricados por cada compañía. De este modo se hizo necesario la creación de un lenguaje común que abstrajera las características técnicas de cada marca y permitiera la comunicación entre sistemas. En 1982 , Dave Smith, de la empresa Sequential, propuso poner de acuerdo a las grandes compañías para crear un protocolo de comunicación entre sistemas que fuera respetado por el convenio o norma. El fundamento de su idea era poder interconectar los instrumentos digitales para hacer sonar a más de un aparato a la vez, creando así un efecto de polifonía musical. El estándar MIDI fue propuesto inicialmente en un documento dirigido a la Audio Engineering Scoiety. La primera especificación MIDI se publicó en agosto de 1983. El primer sintetizador capaz de soportar la especificación fue el Prophet 600 de Sequential (figura 4.1). En 1996 todos los instrumentos que ostentaban el logotipo respetaban el estándar. MIDI es el acrónimo de “Interfaz Digital de Instrumentos Musicales”, y actualmente el estándar es ampliamente utilizado en la industrial musical; aunque es también muy utilizado en otros muchos sistemas, no sólo instrumentos musicales. Así, por ejemplo, podemos encontrar la tecnología MIDI implementada en sistemas para controlar la secuencia de luces de un espectáculo en directo o controlar aparatos de edición de sonido, entre otras muchas aplicaciones. 81 C A P Í T U L O 4 • Introducción a MIDI Figura 4.1 Prophet 600 de Sequential 4.1.2 Conceptos básicos La información MIDI define diversos tipos de datos como números que pueden corresponder a notas particulares, números de programas de sonidos o valores de controladores. Gracias a esta simplicidad, los datos pueden ser interpretados de diversas maneras y utilizados con fines diferentes a la música. El protocolo incluye especificaciones complementarias de hardware y software. Así, es posible la interconexión de una gran número heterogéneo de sistemas software y hardware, tal como se muestra en la figura 4.2. Permite, por ejemplo, reproducir y componer música en el formato MIDI. Los archivos almacenados en disco se caracterizan por su calidad de muestreo y su poco espacio en bytes requerido. Un fichero de una canción puede oscilar entorno a los 17 Kilobytes. Aunque este estándar de fichero no es comúnmente utilizado en Internet. Figura 4.2 Interconexión de sistemas MIDI 82 C A P Í T U L O 4 • Introducción a MIDI Cabe aclarar que MIDI no transmite señales de audio, sino datos de eventos y mensajes controladores que se pueden interpretar de manera arbitraria, de acuerdo con la programación del dispositivo que los recibe. Es decir, MIDI es una especie de “partitura” que contiene las instrucciones en valores numéricos, o más específicamente en binario, sobre cuándo generar cada nota de sonido y las características que debe tener; posteriormente el sistema MIDI transformará en música las secuencia de instrucciones. Los aparatos MIDI se pueden clasificar en tres grandes categorías: Controladores: generan los mensajes MIDI (activación de nota, desactivación de nota). El controlador más familiar para los músicos tiene forma de teclado de piano, que es el más utilizado; aunque también se puede encontrar en guitarras eléctricas, órganos de tubos, clarinetes, incuso gaitas. Figura 4.3 Controlador MIDI Unidades generadoras de sonido: también conocidas como módulos de sonido. Suelen ser unidades centrales de procesamiento de audio que reciben datos MIDI y generan sonido. Figura 4.4 Módulo MIDI Secuenciadores: son sistemas software o hardware dedicados a reproducir un fichero con especificación General MIDI 1.0 (GM1) o General MIDI 2.0 (GM2). Figura 4.5 Secuenciador MIDI 83 C A P Í T U L O 4 • Introducción a MIDI 4.1.3 Electrónica MIDI 4.1.3.1 Interconexión de sistemas MIDI no ha sido indiferente a los nuevos avances en sistemas de comunicación electrónica de datos. Así, las nuevas tecnologías en comunicaciones han abierto un gran abanico de posibilidades al estándar, del cual se ha dejado influenciar. El sistema MIDI utiliza un total de 16 canales que se pueden comunicar entre aparatos o instrumentos a través de cada cable. Los tipos de conexión actuales son los siguientes: Conectores y cables MIDI: El MIDI comunica de manera digital los datos de una interpretación musical a través de un cable estándar MIDI como una serie de mensajes que se transmiten a una velocidad de 31,25 kbaudios (bits/segundo). Figura 4.6 Cable MIDI El sistema de funcionamiento es tipo simplex, es decir, sólo puede transmitir señales en un sentido. La dirección que toman las señales es siempre desde un dispositivo maestro hacia un dispositivo esclavo. El primero genera la información y el segundo la recibe. Un cable estándar MIDI consiste en un cable mutipolar trenzado blindado terminado en un conector DIN de cinco pines. Los pines 4 y 5 se utilizan para conducir los datos y el pin 2 para conectar el blindaje del cable a la toma de tierra. Los pines 1 y 3 no se utilizan actualmente, pero están reservados para futuros propósitos. El cable trenzado y el blindaje metálico se utilizan para reducir las interferencias electromagnéticas del exterior. La especificación del cable MIDI obliga a una longitud máxima del cable de 15 metros para evitar la degradación de señal y la interferencia externa; aunque las longitudes típicas son de 1,3 y 6 metros de largo. En la figura 4.6 pueden apreciarse los conectores MIDI estándar. En los sistemas MIDI se utilizan tres tipos de puerto: 84 C A P Í T U L O 4 • Introducción a MIDI • • • MIDI in: es la entrada de datos al dispositivo. MIDI out: salida de datos hacia otro dispositivo. MIDI thru: Puente de datos. Transmite una copia exacta de los datos que llegan por MIDI in a otro instrumento o aparato MIDI que continúa la cadena de datos MIDI conectada. Figura 4.7: Puertos MIDI Algunos dispositivos no tiene puerto thru, por lo que deben emularlo por software. Sólo hay dos formas de conectar un sistema MIDI a otro. Son las siguientes: • • Conectar el MIDI out al MIDI in del otro sistema. Conectar el puerto MIDI thru de un sistema al puerto MIDI in del otro sistema. Una forma típica de conectar dispositivos MIDI es un una red margarita o daisy chain. Este método transmite MIDI de un aparato al siguiente puenteando los datos recibidos por MIDI in de un aparato, directamente a otro aparato por MIDI thru, donde la cadena continúa en el siguiente aparato. En la figura 4.8 se muestra un ejemplo de esta disposición. Figura 4.8: Conexión en margarita (daisy –chain) Esta conexión permite al dispositivo 1 conectarse al dispositivo 2, que repite los datos hacia el dispositivo 3 y así sucesivamente. 85 C A P Í T U L O 4 • Introducción a MIDI Conexiones USB y FireWire: Actualmente los sistemas MIDI pueden conectarse también a través de conectores USB del tipo 1 y 2 y el protocolo IEEE (FireWare). Gracias a estos dispositivos de alta velocidad es posible conectar, respetando el protocolo MIDI 1.0, aparatos de diferentes compañías. La ventaja de estos conectores es la facilidad de auto-detección del dispositivo, la rápida y cómoda conexión. Conexiones de red mLAN: Este innovador sistema fue creado por Yamaha y resolvía el problema de la innecesaria parafernalia de cables para la conexión MIDI. Este sistema permite que el audio multicanal y los datos sean transferidos a través del cable estándar IEEE FireWare 1394. mLAN, soporta un total de 100 canales de datos de audio digital y hasta 256 puertos MIDI, es decir, un total de 16 x 256 conexiones o canales en tiempo real. Además tiene la posibilidad de comunicación fullduplex a velocidades de 100, 200 ó 400 Mbps. Las ventajes principales de este sistema son la velocidad de conexión, el ancho de banda para audio y canales MIDI, y la fácil conexión automática entre sistemas MIDI sin necesidad de configuración. Figura 4.9 Sistema mLAN 86 C A P Í T U L O 4 • Introducción a MIDI 4.1.3.2 Esquemáticos En esta sección se ilustra los esquemáticos electrónicos para configurar las conexiones IN, OUT y THRU a la circuitería MIDI basada en un integrado UART para comunicación serie. Figura 4.10 Esquemático MIDI Como puede observarse en la figura 4.10, MIDI es un interfaz serie asíncrono. La frecuencia de baudios es 31.25 Kbaudios (+/- 1%). Hay 1 bit de comienzo, 8 bits de datos y 1 bit de parada (en total 10 bits), lo cual hace un periodo de 320 microsegundos por byte serie. La intensidad de corriente en el bucle del circuito MIDI es de 5mA. Para evitar daños en el aparato en caso de una diferencia de tensión excesiva con el transmisor, la entrada está optoaislada. Los optoacopladores Sharp PC-900 y HP6N138 garantizan una respuesta binaria bien diferenciada para evitar errores de datos. Los flancos de subida y bajada de los optoacopladores deben estar por debajo de 2 microsegundos. La transmisión MIDI-thru podría no ser realizada correctamente debido al tiempo de retardo (causado por el tiempo de respuesta del optoacoplador) entre los flancos de subida y bajada de la onda cuadrada. Estos errores de temporización tenderán a incrementarse de forma progresiva cuantos más dispositivos estén conectados en estructura de margarita (daisy-chain) a otros conectores MIDI-thru. Por lo tanto, esto limita el número de dispositivos que pueden encadenarse en esta distribución. 87 C A P Í T U L O 4 • Introducción a MIDI 4.2 Especificación MIDI 4.2.1 Introducción De acuerdo a la especificación MIDI, los mensajes están compuestos de varios bytes que se transmiten en serie y contienen un conjunto de instrucciones o de datos de control a uno o a varios sistemas. Los dos bytes fundamentales del sistema MIDI son: • • Byte de estado. Byte de datos. El byte de estado se utiliza para indicar el tipo de mensaje y el canal dentro del sistema al que va dirigido. En los bytes de datos se codifican en binario los valores numéricos que indican los parámetros para ese canal. Por ejemplo: la nota pulsada y la velocidad de pulsación. Figura 4.11. Para identificarlo, el MSB (bit más significativo) del byte de estado es siempre 1, en contrapartida, el MSB del byte de datos siempre empieza en 0. Byte de estado Nº de estado/canal (1001 0100) (note on/canal 5) Byte de datos 1 Nº de nota (0100 0000) (64) Byte de datos 2 Velocidad de pulsación (0101 1001) (89) Figura 4.11 Mensaje MIDI 4.2.2 Canales MIDI Con el uso de canales MIDI, un mensaje generado por un sistema puede dirigirse a otro sistema por medio de la identificación de canales. Esto se consigue utilizando cuatro bits dentro del byte de estado. Como son cuatro bits en el campo de estado, tenemos un total de 24 = 16 canales MIDI direccionables. Con el uso de canales podemos asignar un instrumento a cada canal y enviarlo a través del sistema. En la figura 4.12, puede verse esta situación representada dentro del círculo en rojo. Tenemos dos dispositivos: el GPO Studio 2 y la tarjeta SB Creative Live, en los cuales hemos asignados dos instrumentos de percusión sobre el mismo canal: Tambourine y Splash Cymbal 2 que sonarán simultáneamente cuando reciban datos sobre el canal 10, aunque estén en aparatos diferentes. 88 C A P Í T U L O 4 • Introducción a MIDI Figura 4.12 Asignación a canales Utilizando como ejemplo la figura 4.12 se podría crear una composición corta utilizando un sintetizador como estación de trabajo conectado a un secuenciador (un software capaz de grabar, editar y reproducir datos MIDI), un módulo de sonido y un sampler. De esta manera es posible que el ordenador envíe mensajes MIDI al módulo de sonido, al sintetizador o al sampler, controlando por medio de cada canal los instrumentos en cada dispositivo antes mencionado. 89 C A P Í T U L O 4 • Introducción a MIDI 4.3 Mensajes MIDI En el apéndice A1 puede consultarse la tabla resumida de los diferentes tipos de mensajes MIDI. A continuación se detallan ampliamente cada uno de estos tipos. 4.3.1 Mensajes de canal Son utilizados para transmitir datos de interpretación en tiempo real, puesto que son generados cuando un instrumento MIDI es tocado en directo. Los mensajes de canal contienen un número de canal en su byte de estado. A continuación se explican los siete tipos de mensajes: Note on: indica que una nota particular debería ser reproducida. Esencialmente, significa que la nota comienza a sonar, aunque algunos patches podrían tener un periodo de ataque VCA largo que necesite aumentar lentamente el sonido. En cualquier caso, este mensaje indica que una nota particular comienza a sonar (al menos que la velocidad de pulsación de la nota sea 0). Si el dipositivo el multitímbrico, cada nota se reproduce en un canal diferente. El rango del byte de estado abarca los valores hexadecimales 0x90 a 0x9F. El primer byte de datos contiene el número de la nota. Hay 128 posibles notas en un sistema MIDI, numeradas del 0 al 127. El valor 60 corresponde a la nota DO central del teclado. El segundo byte de datos es la velocidad, también en el rango [0,127]. Indica con qué fuerza se ha pulsado la tecla. Con frecuencia la velocidad se usa para adaptar el tiempo de ataque del VCA. Un mensaje MIDI con la velocidad 0 es considerado como un mensaje de Note off. Note off: indica que una nota en concreto ha sido finalizada. Esto significa que la nota debe de parar la reproducción, aunque algunos patches podrían tener un tiempo de liberación (release time) que necesite disminuir lentamente el sonido. Algunas veces, el pedal de sostenimiento puede estar presionado, en ese caso la liberación de la nota es pospuesta hasta que se levante el pedal. En cualquier evento, este mensaje puede causar que el VCA se posicione dentro de la etapa de liberación, o si el pedal está presionado, indica que una nota debería ser liberada cuando el pedal de sostenimiento esté desactivado. En un dispositivo multitímbrico cada note off debería activarse por cada canal. El mensaje contiene un byte de estado con el rango de valores: 0x80 a 0x8F, mientras que el primer byte de datos contiene la tecla a liberar y el segundo byte la velocidad en la que se ha dejado levantar la tecla. 90 Aftertouch: cuando una determinada nota está reproduciéndose puede serle aplicada una presión. Muchos instrumentos MIDI tiene electrónica sensible a la presión que pueden detectar cómo de fuerte presiona un músico la tecla. El músico puede variar esta presión, incluso cuando continúa presionando la tecla. El mensaje de aftertouch expresa la cantidad de presión en un determinado punto de una tecla. Puesto que el músico pude variar continuamente la presión, los dispositivos que generan aftertouch normalmente envían varios mensajes C A P Í T U L O 4 • Introducción a MIDI durante el proceso. Una vez recibido el mensaje, muchos dispositivos lo usan para variar el VCA de la nota y/o el nivel de sostenido de la envolvente VCF, o la cantidad a ser aplicada al control LFO. El byte de estado está comprendido en el rango de valores: 0xA0 a 0xAF. El primer byte de datos es el número de nota a aplicar la presión. El segundo byte de datos es la cantidad de presión, en un rango discreto de [0,127] valores. Channel preassure: este mensaje expresa la cantidad general (media) de presión en las teclas en un determinado punto. Al igual que en el mensaje de aftertouch, los músicos pueden variar continuamente la presión, generándose múltiples mensajes de este tipo. El comportamiento al recibir el mensaje de este efecto es el mismo que en el aftertouch. Pero, ¿cuál es la diferencia entre el mensaje de aftertouch y channel presassure? La diferencia principal recae en que el mensaje aftertouch es para teclas individuales, es decir, un mensaje aftertouch solamente afecta a la nota cuyo número está en el mensaje. Cada tecla que se presiona genera su propio mensaje aftertouch. Si presionamos una tecla más fuerte que otra, esa tecla sola generará mensajes aftertouch con valores más altos que la otra tecla. El resultado neto es que algunos efectos se aplicarán a una tecla más que a otra. En contrapartida, el mensaje channel preassure es enviado para el teclado entero. Así, si presionamos una tecla más fuerte que otra, el sintetizador promediará la diferencia entre las dos presiones, y luego simplemente produce el efecto de presionar las dos teclas con la misma presión exacta. Un sistema MIDI usa normalmente uno de los dos efectos separadamente. Muchos controladores MIDI no generan aftertouch ya que requiere un sensor de presión individual por cada tecla, y esto encarece el diseño y el producto. Por esta razón, algunos dispositivos sólo implementan el mensaje de channel preassure. Program change: se utiliza para cambiar un programa determinado1. La mayoría de los módulos de sonido tienen una gran variedad de instrumentos. Cada uno de estos instrumentos está contenido en un programa. Así, cambiando el programa cambia el instrumento que suena cuando una nota es presionada. Obviamente, los mensajes MIDI pueden cambiar el programa. En MIDI hay un total de 128 posibles programas, numerados en el rango [0,127] (véase apéndice A3). Si el dispositivo es multitímbrico, normalmente puede reproducir 16 partes a la vez en cada canal. Existen algunos sistemas MIDI que no tienen instrumentos, por ejemplo, una unidad de Reverb. En este caso se utiliza para seleccionar el Preset a utilizar. Otro ejemplo sería el de una caja de ritmos, en el que el mensaje se utilizaría para seleccionar un determinado patrón rítmico. En este mensaje el byte de estado comprende los valores del 0xC0 al 0xCF, mientas que sólo transporta un byte de datos, que contiene el programa a establecer. 1 Alguna terminología se refiere al programa como patch, intrumento, preset, etc. 91 C A P Í T U L O 4 • Introducción a MIDI Control change: establece un valor de controlador concreto. Un controlador es cualquier botón, slider, switch etc. que normalmente implementa alguna función más que reproducir o detener notas. Hay un total de 128 posibles controladores en un dispositivo MIDI y están numerados en el rango discreto [0,127] (véase apéndice A2). Alguno de estos números de controlador están asignados a un control hardware particular en el dispositivo MIDI. Por ejemplo, el controlador 1 es asignado a la rueda de modulación o mudulation wheel. Otros números de controlador son libremente asignados para ser arbitrariamente interpretados por el sistema. Por ejemplo, una caja de ritmos podría tener un control slider para manejar el Tempo el cual asigna libremente valores arbitrarios. Cuando la caja de ritmos recibe un mensaje de controlador con ese número de controlador ya puede ajustar el Tempo. Hay que recalcar en este apartado que un dispositivo MIDI no necesita el controlador físico para responder a los mensajes de controladores. El byte de estado del mensaje de cambio de control cae en el rango: 0xB0 a 0xBF, y el primer byte de datos es el número de controlador en el rango [0,127]. El segundo byte especifica el valor a establecer para el controlador determinado. Pitch Wheel: denominado también rueda de modulación. Este mensaje es usado para desplazar la altura tonal de un nota. La rueda de modulación es medida por el décimo cuarto bit. El valor central es 0x2000. Respecto al byte de estado, está dentro del rango 0xE0 a 0xEF, mientras que los dos byte de datos deberían combinarse para formar un valor de 14 bits. Los bits 0 a 6 del segundo byte son realmente los bits del 7 al 13 del valor de 14 bits. De esta forma tendríamos el siguiente procedimiento en C++ para realizar la combinación: unsigned short CombineBytes(unsigned char First, unsigned char Second) { unsigned short _14bit; _14bit = (unsigned short)Second; _14bit <<= 7; _14bit |= (unsigned short)First; return(_14bit); } 92 C A P Í T U L O 4 • Introducción a MIDI 4.3.2 Mensajes de sistema Estos mensajes son transmitidos globalmente a cada dispositivo dentro de la red MIDI, puesto que no se utiliza en ellos el número de canal. Cualquier dispositivo responderá a los mensajes del sistema independientemente del canal. A continuación se enumeran los diferentes mensajes de sistema MIDI: System exclusive: sirve para comunicar con el dispositivo información independiente del protocolo MIDI. En la sección siguiente (4.3.3) se examinará este mensaje con más detenimiento. Song Position Pointer: algunos dispositivo maestros que controlan la secuencia de reproducción envían este mensaje para forzar a un dispositivo esclavo a poner en la cola una cierta posición de la secuencia. Este mensaje no comienza inmediatamente la reproducción, simplemente pone al dispositivo preparado para la reproducción en una particular posición en la secuencia. El byte de estado tiene el valor 0xF2 y los bytes de datos, al igual que en el caso de la rueda de modulación, tienen que ser combinados en un valor de 14 bits. El valor de 14 bits es el MIDI Beat en el que comenzar la secuencia. Se asume que la secuencia comienza en el MIDI Beat 0. Cada MIDI Beat utiliza 6 pulsos MIDI. Es decir, cada MIDI Beat es un deciseisavo de nota. Ejemplo: Si el valor del Song Position Pointer es 8, el secuenciador debería poner en la cola al tercer cuarto de nota de la secuencia: 8 MIDI Beats x 6 pulsos de reloj MIDI por Beat = 48 pulsos MIDI ya que hay 24 pulsos en ¼ de nota. Song select: los dispositivos maestros MIDI envían este mensaje para comenzar el “playback” de una canción. El byte de estado es 0xF3, y el byte de datos es una valor comprendido entre 0 y 127 que indica el número de secuencia o canción. Tune request: mensaje que también se traduce como petición de tono. Este mensaje se utiliza para solicitar al dispositivo un cambio de calibración tonal. Con frecuencia se implementa en módulos de sonido con circuitos osciladores analógicos. El byte de estado es el valor 0xF6 y no contiene byte de datos. End of system exclusive: indica el final de un mensaje de sistema exclusivo. 93 C A P Í T U L O 4 • Introducción a MIDI 4.3.3 Mensajes de sistema exclusivo Estos mensajes permiten a los fabricantes y programadores de sistemas MIDI comunicar información específica para la configuración del dispositivo. El principal uso del mensaje es para enviar una gran cantidad de datos en formato no estructurado MIDI, como volcados de la memoria de patches, datos del secuenciador o datos de formas de onda. Por ejemplo, un mensaje SysEx podría se utilizado para establecer el nivel de retroalimentación para un operador en un sintetizador de modelado físico de Roland . El formato de transmisión de un mensaje de sistema exclusivo (figura 4.13), como se define en el estándar MIDI, incluye un byte de estado (arranque) de sistema exclusivo (0xF0) y un byte de parada EOX (0xF7). 0xF0 0x43 0x25 ... ... ... ... ... ... 0xF7 Figura 4.13 Estructura de mensaje de sistema exclusivo El byte más importante después del 0xF0 (SOX) debería ser el identificador de fabricante, así, por ejemplo para la marca Korg tendríamos el byte 0x42 y para la empresa Kurzweil el byte 0x07. A continuación se muestran algunos ejemplos de utilización de los mensajes de sistema exclusivo: • • • • Transmisión de datos de sonido (patches) entre sintetizadores con tabla de ondas. Copia de seguridad de los sonidos actuales almacenados en la EPROM del sintetizador. Este proceso se lleva a cabo haciendo un volcado (dump) de los datos del sintetizador a un ordenador para ser almacenados en un disco u otro tipo de memoria secundaria. Obtención de sonidos de la Web. Internet permite acceder a un gran banco de muestras de sonido (patches) de sistema exclusivo. Tan sólo es necesario descargar el fichero en formato .syx compatible con el modelo de sintetizador y proceder a su envío desde el ordenador a través de un puerto MIDI. Control y edición MIDI en tiempo real basada en sistema exclusivo. Los editores de sonido proporcionados por los fabricantes del dispositivo permiten alterar y enviar cambios de los parámetros digitales a la configuración del sistema. Así, es posible cambiar las variables del sonido, la secuenciación, los osciladores, etc. En la imagen 4.14 puede verse un editor de sonidos para el sintetizador Korg X5D. Es necesario recalcar que los datos de sistema exclusivo tomados de la red o de disco suelen codificarse utilizando varios estilos de formato de archivo de sistema exclusivo (no hay por lo tanto estandarización). Debido a esto, los volcados de memoria se codifican utilizando utilidades software de sistema exclusivo estándar, fácilmente disponibles para Mac o PC (figura 4.14). También es posible encontrar datos de sistema exclusivo en ficheros MIDI estándar. Este sistema almacena los datos en una pista o track del archivo MIDI. 94 C A P Í T U L O 4 • Introducción a MIDI Figura 4.14 Editor de sonidos para envío por sistema exclusivo 4.3.4 Mensajes de modo Este tipo de mensajes se utilizan para enviar al dispositivo receptor parámetros de configuración MIDI para recibir los mensajes de canal. Como se verá a continuación, el modo 3 es el más utilizado, ya que es el más potente, en detrimento del uso actual de los otros modos. • • • • Modo 1 – omni on/poly: Es el más simple de todos. Con este mensaje si un dispositivo envía un mensaje (note on) por el canal 7 y otro por el canal 10, el receptor en modo Omni ignorará los canales por los que lleguen, reproduciendo las dos notas a la vez. Omni on indica que no se consideran los canales y Poly que puede reproducir más de una nota a la vez. Modo 2 – omni on/mono: Es idéntico al modo 1, excepto que sólo permite una nota a la misma vez. Modo 3 – omni off/poly: Es el modo más potente de los cuatro y el más comúnmente usado. Omni off indica que sí considera los canales en que recibe, por lo tanto, selecciona lo que reproduce. Poly indica que es posible reproducir más de una nota a la vez. Modo 4 – omni off/mono: Este modo es básicamente una versión monofónica del modo 3. Esta variante sólo permite una nota a la vez en cada canal. 95 C A P Í T U L O 4 • Introducción a MIDI 4.3.5 Mensajes de tiempo real Los mensajes de tiempo real, que consisten en un solo byte de estado, están comprendidos en el rango [0xF8,0xFF]. Estos mensajes están principalmente relacionados con la temporización y sincronización. MIDI permite que los mensajes de tiempo real sean enviados en cualquier momento, incluso intercalados con otros mensajes MIDI. Por ejemplo, un mensaje en tiempo real podría ser enviado entre los dos bytes de datos de un mensaje note on. Un dispositivo debería siempre estar preparado para manejar cada situación; procesando el byte de tiempo real, y subsiguientemente continuar procesando el mensaje anteriormente interrumpido. A continuación se enumeran los mensajes de tiempo real utilizados en MIDI: MIDI Clock: algunos dispositivos maestros que controlan la reproducción de la secuencia envían este mensaje para mantener al dispositivo esclavo en sincronía con el maestro. Un mensaje de tiempo MIDI se envía a intervalos regulares (basados en el Tempo del maestro) para realizar esta tarea. El mensaje de reloj de tiempo contiene el byte de estado 0xF8, mientras que no contiene ningún byte de datos. En la sección 4.4 comentaremos con más detalle los aspectos relacionados con el reloj y la sincronización MIDI. MIDI Tick: como en el caso anterior, algunos dispositivos maestros envían este mensaje para mantener las sincronía con el receptor. Este mensaje es enviado a intervalos regulares de uno cada 10 ms. El byte de estado contiene el valor 0xF9 y no tiene datos. MIDI Start: indica al dispositivo esclavo que debe comenzar la reproducción de una secuencia o una canción. Comienza siempre en el MIDI Beat 0. El byte de estado es 0xFA y no contiene datos. MIDI Stop: es el opuesto al anterior. Indica al dispositivo esclavo que detenga la secuencia o la canción. El byte de estado es 0xFC y no tiene datos. MIDI Continue: para continuar la reproducción de la secuencia o canción por donde se había detenido anteriormente, o puesto en cola con el mensaje Song Position Pointer. El byte de estado es 0xFB y no contiene datos. Reset: Reinicia el dispositivo a la configuración por defecto como si se hubiera encendido de nuevo. El byte de estado es 0xFF y no tiene bytes de datos. 96 C A P Í T U L O 4 • Introducción a MIDI Active Sense: Este mensaje se envía cada 300 ms si no ha habido actividad en el bus MIDI. Esto permite indicar a los dispositivos que hay una buena conexión entre ellos. El byte de estado es 0xFE y tampoco contiene datos. En la figura 4.15 puede verse el algoritmo del mensaje Active Sense. Se asume que los dispositivos tienen un timer hardware que se incrementa cada milisegundo. Una variable llamada timeout se utiliza para incrementar los milisegundos transcurridos. Otra variable llamada flag se activa cuando el dispositivo recibe un mensaje homónimo de otros dispositivo, y consecuentemente espera a recibir más mensajes Active Sense de ese dispositivo. In t e r r u p c ió n d e l t im e r Interru pció n d e recepció n M ID I F la g = 0 ? R ecib e d atos F la g = 1 In c r e m e n t a t im e o u t Inicia la variable d e tim eou t S A L ID A t im e o u t < = 3 0 0 m s no 0xF E ? ? t im e o u t > 3 0 0 m s 0xFE Establece el flag S AL ID A a algu na o tra o peración D e s a c t iv a t o d a s la s n o ta s S A L ID A In ic ia e l f la g S A L ID A Figura 4.15 Algoritmo Active Sense 97 C A P Í T U L O 4 • Introducción a MIDI 4.4 Sincronización y tiempo MIDI Hay 24 pulsos MIDI en cada cuarto de nota (un cuarto de nota equivale a una negra), 12 pulsos MIDI en un octavo de nota, 6 pulsos en un dieciseisavo, etc. Por lo tanto, cuando un dispositivo esclavo cuenta 24 pulsos de reloj MIDI, sabe que ha transcurrido ¼ de nota. Obviamente, la frecuencia a la que el reloj maestro se incrementa depende del Tempo. Por ejemplo, para un tempo de 120 BPM, es decir, 120 cuartos de nota en cada minuto, el maestro envía un pulso cada 20833 microsegundos. Sabiendo que hay 106 microsegundos en un segundo y que un minuto contiene 60 x 106 microsegundos. En un tempo de 120 BPM, hay 120 cuartos de nota por minuto. Como hay 24 pulsos MIDI en un cuarto de nota, en consecuencia, debería haber 24 x 120 pulsos MIDI por minuto. Por lo tanto, cada pulso MIDI es enviado a una frecuencia de 60 x 106 / (24 x 120) microsegundos. El código de tiempo MIDI o MIDI time code (MTC) incorpora el formato de tiempo absoluto: horas, minutos, segundos y cuadros, al flujo de datos MIDI. Es uno de los más recientes códigos de tiempo utilizados en MIDI. Con el mensaje Song Position Pointer se pueden sincronizar las secuencias o canciones entre dispositivos MIDI con suficiente exactitud, aunque surgen problemas cuando hay que sincronizarlos, por ejemplo, con grabadoras de vídeo o procesadores de sonido. El código de tiempo utilizado internacionalmente para estos aparatos es el SMPTE que es el acrónimo de Society of Motion Pictures & Television Engineers y utiliza, al igual que MTC el tiempo absoluto. El formato SMPTE es muy eficaz y permite sincronizar con mucha eficacia aparatos muy heterogéneos. Es posible convertir el formato MTC a SMPTE y al contrario. Figura 4.16 Secuenciador MIDI Rosegarden 98 C A P Í T U L O 4 • Introducción a MIDI Bibliografía y referencias Libros Técnicas de Grabación modernas – David Miles Huber, Robert E. Runstein, 6ª edición. Editorial Omega, 2007. Diseño y desarrollo Multimedia, Sistemas, Imagen, Sonido y Vídeo – Manuel-Alonso Castro Gil, Antonio Colmenar Santos, Pablo Losada de Dios, Juan Peire Arroba. Editorial Ra-ma, 2002. La Web MIDI Manufacturers Association: http://www.midi.org/ MIDI specification: http://home.roadrunner.com/~jgglatt/ Wikipedia: http://es.wikipedia.org/wiki/MIDI Lista de figuras obtenidas de Internet http://www.amazona.de/media/articles/article_images/article_697/1_prop het600.jpg Figura 4.2 http://www.nopianonoproblem.com/files/other/MIDI_SetupOptions_Larg e.jpg Figura 4.3 http://www.etcetera.co.uk/products/images/EMU300big.jpg Figura 4.4 http://www.generalmanual.com/img/0902/roland-jv-2080-synthesizermodule.jpg Figura 4.8 http://www.fortunecity.com/emachines/e11/86/graphics/midi/MIDI5.gif Figura 4.9 http://www.savedbytechnology.com/2003/yamaha_my16-mlan.jpg Figura 4.14 Software del sintetizador Korg X5D http://www.les-stooges.org/pascal/midiswing/index.php Portada Figura 4.1 99 C A P Í T U L O 5 • Programación MIDI 5 Programación MIDI 5.1 Introducción Una vez examinados los conceptos básicos sobre la tecnología MIDI es momento de programar el sistema para modificar en tiempo real eventos y mensajes. De esta forma, es posible realizar aplicaciones que se ajusten a las demandas actuales de edición de música, así como programas de control de dispositivos a través de mensajes MIDI. La programación MIDI se ha vuelto fundamental en la programación de sistemas, pues se utiliza en un amplio rango de aplicaciones, desde investigación hasta videojuegos. Actualmente se hace necesaria alguna herramienta que permita a los ingenieros y programadores desarrollar software musical y de acceso a las funciones básicas de los dispositivos MIDI. De esta manera, se consigue una abstracción del software de bajo nivel, proveyendo una arquitectura orientada a objetos y estratificada que permita el diseño de software robusto y complejo compuesto por una gran cantidad de componentes. En los principios de los años 90, desarrollar una aplicación de acceso a MIDI sobre una plataforma de 16 bits como era Atari (muy utilizado en la industria en aquella época), suponía desarrollar una aplicación basada en una ingeniería de software no orientada a objetos, pero estructurada y modular, implementada en un lenguaje como C donde el acceso al hardware MIDI del computador se hacía a través de funciones de envió de bytes a puertos, y en su extremo, programando directamente en ensamblador. Posteriormente, con la aparición de los sistemas operativos modernos de 16 y 32 bits como Windows, Linux y Mac/OS, han provisto una API (Interfaz de Programación de Aplicaciones) al servicio de los programadores que, además de abstraer la complejidad del acceso al hardware (manejado por las capas de E/S y la máquina virtual del sistema operativo) proporciona un conjunto de servicios para acceder a todas las funciones que MIDI posee. La ventaja de este enfoque moderno permite desarrollar software MIDI que funcionará con cualquier fabricante de hardware sin la necesidad de modificar el código fuente del programa. Actualmente en Windows existe la posibilidad de desarrollar aplicaciones MIDI utilizando dos APIs diferentes: la Windows Multmedia API y la API DirectMusic de DirectX. En este estudio nos centraremos en esta última. Otras librerías MIDI de alto nivel profesionales para C++ existentes en Internet son: • • • • CLAM: C++ library for audio music. Desarrollada en la Universidad Pompeu Fabra de Barcelona por el Grupo de Tecnología Musical. The Synthesis Toolkit in C++: Desarrollado por la Universidad de Standford. Maximum MIDI music applications in C++. Libro sobre MIDI que contiene una librería sobre la API Windows Multimedia. DirectMIDI: Librería de software libre que se explicará en este capítulo. 101 C A P Í T U L O 5 • Programación MIDI 5.2 La API DirectMusic de DirectX Como se explicó en el capítulo 2, DirectMusic es un componente más de la librería de alto rendimiento para multimedia DirectX, junto con DirectSound y DirectShow. DirectMusic permite que los efectos de sonido y la música sean compuestos y reproducidos con un control flexibe e interactivo. Arquitectónicamente, DirectMusic es un conjunto de objetos de alto nivel, construidos sobre DirectSound que permite la reproducción de sonido y música sin la necesidad de utilizar las funciones de bajo nivel de DirectSound. DirectMusic trabaja con datos de música basados en mensajes. La música puede ser sintetizada vía hardware directamente con los puertos que proporciona el fabricante o con el Sintetizador software de Microsoft. La historia de la API de DirectMusic se remonta al año 1996 cuando fue lanzado por primera vez como un componente ActiveX, llamado Iteractive Music Architecture (IMA). Fue introducido inicialmente como parte de la versión 6.1 de DirectX en febrero de 1999 e incluido en todos los sistemas operativos Windows 98 segunda edición. 5.2.1 Características principales DirectMusic provee un sistema completo para la implementación de soundtracks (pistas) utilizando las ventajas de la aceleración hardware, sonidos descargables (downloadable sounds) y los Objetos Media DirectX (DMOs), posicionamiento avanzado de efectos en 3D, entre otras muchas más características. En DirectMusic, la música es generada en tiempo real y no de forma estática, proporcionado funcionalidades para la reproducción con variaciones y responder a eventos flexibles de programas vía MIDI. Entre las características avanzadas caben destacar: • • • • • • • • 102 Carga y reproducción de sonidos desde ficheros o recursos MIDI, WAV y ficheros propietarios. Monitorización del tiempo de los eventos musicales con alta precisión. Permite además la captura del tiempo de los datos MIDI en el momento de su recepción utilizando un reloj de referencia del sistema de alta resolución. Permite que la música y los efectos sean rápidos y dinámicamente cambiados en tiempo real en respuesta a eventos del usuario. En este aspecto, resuelve los problemas de rendimiento de la API Multimedia básica de Windows(función midiout). Reproduce múltiples fuentes de sonido a la vez. Envía eventos de tempo, cambios de programa (patch) y otros eventos MIDI de forma programada. Utilización de los sonidos descargables (Downloadable sounds), un estándar de La Asociación de Fabricantes MIDI (MMA), permitiendo a los desarrolladores la salida de datos de audio de tabla de ondas sobre hardware de audio no equipado con síntesis de tabla de ondas. En los ordenadores sin un hardware sintetizador de tabla de ondas, el Sintetizador Software de Microsoft permite emular un hardware de tabla de ondas, consiguiendo que las aplicaciones tengan un resultado homogéneo en todos los sistemas. Posicionamiento del sonido en un espacio tridimensional. C A P Í T U L O 5 • Programación MIDI • • • • Posibilidad de aplicar efectos de sonidos como Chorus, Reverb, Delay, Flanger, Echo, Distortion, Gargle, etc. Uso de más de 16 canales MIDI. Por medio de la utilización de los objetos performance es posible reproducir hasta 216 ó 65536 canales MIDI. Con la utilización de los audiopaths los efectos de espacialización pueden ser aplicados individualmente a cada sonido. Captura de datos MIDI, permitiendo la redirección del flujo de un puerto de entrada a otro de salida. 5.2.2 Principales interfaces COM Como pudimos ver en el capítulo 2, DirectX está basado en tecnología COM (Modelo de objetos componentes) que se describió detalladamente en la sección 2.1.6. DirectMusic está compuesto de varias interfaces COM que abstraen la funcionalidad de las aplicaciones musicales MIDI. Las principales interfaces son las siguientes: • IDirectMusic8: provee métodos para manejar buffers, puertos y el reloj maestro. Sólo debería haber una instancia de esta interfaz por aplicación. • IReferenceClock: esta interfaz estándar proporciona acceso al reloj maestro que es un timer hardware en modo kernel con una alta resolución y es usado para sincronizar todo el playback de audio en el sistema. El método IReferenceClock::GetTime devuelve el tiempo como un entero de 64 bits (definido como un tipo REFERENCE_TIME) en incrementos de 100ηs o nanosegundos. • IDirectMusicPort8: proporciona acceso al objeto DirectMusicPort, que representa un dispositivo que recibe y envía datos MIDI, por ejemplo, el puerto de entrada de un MPU-401, el puerto de salida de un MPU-401 o el sintetizador software de Microsoft. • IDirectMusicThru8: permite la redirección de mensajes de un puerto de entrada a otros puertos de salida. El método IDirectMusicThru8::ThruChannel es utilizado para establecer o romper la conexión thru desde un canal de entrada en un puerto MIDI a otro canal de salida de otro puerto MIDI. • IDirectMusicBuffer8: representa un buffer que contiene datos (normalmente en forma de mensajes MIDI) para ser secuenciados por un puerto. El buffer contiene una pequeña cantidad de datos(normalmente menos de 200ms). Este buffer es creado con al menos 32 bytes de datos MIDI estándar. • IDirectMusicLoader8: se usa para la carga de objetos DirectMusic tales como segmentos, ficheros MIDI, ficheros WAV y DLS. • IDirectMusicCollection8: maneja un conjunto de instrumentos de un fichero DLS y contiene métodos para descargarlos a un puerto de un sintetizador. 103 C A P Í T U L O 5 • Programación MIDI • IDirectMusicIntrument8: esta interfaz representa un instrumento individual de una colección DLS que es descargado posteriormente a un sintetizador usando el método IDirectMusicPort8::DownloadInstrument. • IDirectMusicDownloadedInstrument8: es usada para identificar un instrumento descargado en un sintetizador. El puntero a la interfaz es usado más tarde para liberar el instrumento de la memoria del sintetizador a través de una llamada al método IDirectMusicPort8::UnloadInstrument. • IDirectMusicPortDownload8: permite a una aplicación comunicarse directamente con un puerto que soporte descarga DLS para descargar bloques de memoria directamente al puerto. • IDirectMusicDownload8: representa un bloque contiguo de memoria utilizado para la descarga a un puerto DLS. 5.3 Desarrollando aplicaciones con la librería DirectMIDI 5.3.1 Introducción La librería DirectMIDI es una colección de clases C++ basadas en DirectMusic con la intención de mejorar el desarrollo software bajo la tecnología de audio y MIDI. La capa software está diseñada con una precisa orientación a objetos para facilitar la construcción de aplicaciones e integración en la arquitectura. El framework proporciona, además, un sistema de prevención de errores y su facilidad de uso hace que DirectMIDI sea la herramienta ideal para desarrolladores que están buscando una librería de audio y MIDI estable, amigable, segura y en el estado del arte. El proyecto se encuentra en fase beta aunque hay una versión estable desde la 2.3. Desde la versión 2.1 se considera además software libre, liberada bajo los términos de GNU(General Public License) y publicada como artículo en la revista electrónica sobre programación CodeProject. El diseñador y programador de la librería es el autor de este proyecto, que dedicó dos años a su desarrollo y evaluación íntegros. Actualmente se puede acceder a los ficheros fuente desde el repositorio de software libre de SourceForge, en la URL: http://directmidi.sourceforge.net 104 C A P Í T U L O 5 • Programación MIDI Figura 5.1 Página principal de la librería DirectMIDI Figura 5.2 Aplicación de muestra de la librería DirectMIDI 105 C A P Í T U L O 5 • Programación MIDI 5.3.2 Esquema de la arquitectura DirectMIDI El núcleo principal de la librería está basado en sus diez clases relacionadas que definen los diferentes objetos involucrados en una aplicación basada en MIDI y que encapsulan el código para llevarlo a cabo. El siguiente diagrama de colaboración muestra los objetos creados por una aplicación que usa DirectMIDI: Exceptions CMasterClock CInputPort CReceiver CDirectMusic CDLSLoader CCollection1..i COutputPort CInstrument1..j Initialize DownLoad / UnLoad DirectMusic main objects MIDI ports External MIDI - Synthesizer with SysEx Events DLS support CDMusicException CSampleInstrument 1..n Exception handling Figura 5.3 Diagrama de colaboración de objetos Como podemos observar, hay un objeto del tipo CDirectMusic que encapsula la inicialización COM de una aplicación Win32. Este objeto es el responsable de iniciar los objetos que representan los puertos MIDI que se dividen en dos categorías: puertos de entrada para mensajes de entrada MIDI tales como mensajes de sistema exclusivo o mensajes MIDI 1.0 y puerto de salida MIDI para enviar mensajes en formato sistema exclusivo o MIDI 1.0. Hay un objeto adicional llamado CMasterClock que proporciona una enumeración y selección de un timer hardware como reloj maestro. Hay otros tres objetos relacionados con el objeto COutputPort directa e indirectamente, este es el caso del objeto CDLSLoader que es el responsable de cargar ficheros DLS para almacenarlos en un objeto CCollection. Este objeto representa un conjunto de instrumentos en formato DLS 1.0 ó 2.0 y permite extraer sus instrumentos a mejores contenedores para ellos, llamados objetos CInstrument. Estos son los responsables de mantener una instancia de un objeto particular para un mejor control y organización. Una vez se hayan seleccionado todos los instrumentos de la colecciones, se puede proceder a descargarlos a un programa MIDI específico en un sintetizador para reproducirlos. Además del objeto CInstrument, hay otro objeto similar proporcionado por la librería DirectMIDI que permite almacenar formas de onda en formato WAV o en formato PCM generadas de forma programática. Este objeto, llamado CSampleInstrument, 106 C A P Í T U L O 5 • Programación MIDI proporciona funciones de ayuda para ajustar envolventes, LFOs (Osciladores de baja frecuencia) y regiones (zonas activas del teclado) antes de descargarlos al puerto de salida. Finalmente, el objeto CDMusicException maneja todas las excepciones producidas en la aplicación y muestra información detallada sobre el problema que generó el error. 5.3.3 Comenzando la aplicación 5.3.3.1 Primer paso: Configurar el entorno de desarrollo Es posible comenzar la aplicación en muchos tipos diferentes de proyectos con Visual Studio y la librería DirectMIDI, tales como una aplicación MFC, Win32 básica o Win32 en modo consola1. Para poder compilar una aplicación DirectMIDI necesitamos añadir al proyecto los siguientes ficheros cabecera: CDirectMidi.h, CDirectBase.h y CMidiPart.h. Por último es necesario añadir también al proyecto todos los ficheros .cpp que aparecen dentro del directorio directmidi. También se requiere tener instalada una versión actualizada del SDK de DirectX, en este caso se ha utilizado la versión 9.0c. Finalmente necesitamos añadir al IDE de Visual C++ las rutas de las dependencias (.lib) y ficheros cabecera (.h) del SDK de DirectX, que normalmente se encuentran localizados en los directorios lib que incluye el paquete de instalación. 5.3.3.2 Segundo paso: Las primeras líneas de código El compilador debería saber cual es el código externo que se está utilizando en el fichero .cpp actual de trabajo. Por ello se debe utilizar la directiva #include para indicar al compilador qué otros recursos fuente son necesarios para la compilación. Las cabeceras (includes) requeridas se muestran en el siguiente código: // Cabeceras ANSI I/0 #include <conio.h> #include <iostream> #include <math.h> // La librería DirectMIDI #include ".\\DirectMidi\\CDirectMidi.h" // Inclusión de #pragma comment #pragma comment #pragma comment #pragma comment la librerías necesaria en línea (lib,"dxguid.lib") // Definiciones GUID (lib,"winmm.lib") (lib,"dsound.lib") (lib,"dxerr9.lib") using namespace std; using namespace directmidi; // Tamaño máximo para los datos de System Exclusivo 1 Preferiblemente se utilizará el IDE Microsoft Visual C++ .NET 7.1. 107 C A P Í T U L O 5 • Programación MIDI const int SYSTEM_EXCLUSIVE_MEM = 48000; // Define PI const double PI = 3.1415926; La directiva #pragma comment indica al linkador que cree un fichero obj con las librerías necesarias. Las últimas líneas de código indicadas anteriormente contienen constantes simbólicas necesarias para el ejemplo. 5.3.3.3 Tercer paso: preparando la captura de música La clase CInputPort es la responsable de manejar eventos de entrada MIDI. Estos eventos MIDI son capturados por un thread que invoca a dos métodos virtuales puros sobrecargados de la clase CReceiver; dependiendo del tipo de dato MIDI llegado al puerto. Estos dos diferentes tipos de datos pueden ser: datos MIDI no estructurados (en forma de Sistema Exclusivo) y datos MIDI estructurados (mensajes MIDI Standard 1.0). Para sobrecargar estas funciones virtuales puras, es necesario derivar una clase de CReceiver como se muestra en el siguiente fragmento de código: // Clase heredada de CReceiver class CDMReceiver:public CReceiver { public: // Funciones sobrecargadas void RecvMidiMsg(REFERENCE_TIME rt,DWORD dwChannel,DWORD dwBytesRead,BYTE *lpBuffer); void RecvMidiMsg(REFERENCE_TIME rt,DWORD dwChannel,DWORD dwMsg); }; Una vez realizada esta operación, podemos implementar parte del código de procesamiento de eventos: // Función sobrecargada para la captura de datos en Sistema Exclusivo void CDMReceiver::RecvMidiMsg(REFERENCE_TIME lprt,DWORD dwChannel,DWORD dwBytesRead,BYTE *lpBuffer) { DWORD dwBytecount; // Imprime el buffer recibido for (dwBytecount = 0;dwBytecount < dwBytesRead;dwBytecount++) { cout.width(2); cout.precision(2); cout.fill('0'); cout << hex <<static_cast<int>(lpBuffer[dwBytecount])<< " "; if ((dwBytecount % 20) == 0) cout << endl; if (lpBuffer[dwBytecount] == END_SYS_EX) cout << "\nMemoria del sistema volcada" << endl; 108 C A P Í T U L O 5 • Programación MIDI } } // Función sobrecargada para la captura de datos MIDI estructurados void CDMReceiver::RecvMidiMsg(REFERENCE_TIME lprt,DWORD dwChannel,DWORD dwMsg) { unsigned char Command,Channel,Note,Velocity; // Extrae parámetros MIDI del mensaje CInputPort::DecodeMidiMsg(dwMsg,&Command,&Channel,&Note,&Velocit y); if (Command == NOTE_ON) //Channel #0 Note-On { cout << " Received on channel " << static_cast<int>(Channel) << " Note " << static_cast<int>(Note) << " with velocity " << static_cast<int>(Velocity) << endl; } } La primera función lee los datos del buffer en formato Sistema Exclusivo, imprime los valores en base numérica hexadecimal y detecta cuándo el sintetizador alcanza el final del volcado de datos (el byte 0xF7 de fin de datos exclusivos). Es importante saber que no todos los datos SysEx son recibidos en una única llamada RecvMidiMsg, se pueden producir múltiples llamadas consecutivas a esta función miembro. La segunda función sobregcargada gestiona mensajes estándar MIDI, tales como note on o cambio de programa, en formato de doble palabra. Si se necesita parsear el mensaje en partes se debe usar el método estático CInputPort::DecodeMidiMsg para extraer cada byte componente MIDI. 5.3.3.4 Cuarto paso: inicilizando objetos En este paso se declaran los principales objetos que serán usados a lo largo de la aplicación. Son lo siguientes: int main(int argc, char* argv[]) { CDirectMusic CDMusic; CInputPort CInPort; CDMReceiver Receiver; COutputPort COutPort; CDLSLoader CLoader; CCollection CCollectionA,CCollectionB; CInstrument CInstrument1,CInstrument2; CSampleInstrument CSample1,CSample2; La primera línea declara un objeto del tipo CDirectMusic que es el responsable de instanciar e inicilizar DirectMusic y será el último objeto en ser destruido. El siguiente objeto es CInputPort que maneja los puertos de entrada. El tercero es el objeto 109 C A P Í T U L O 5 • Programación MIDI CDMReceiver que es una clase derivada de CReciever y que implementa las funciones sobrecargadas vistas en el aparatado anterior. El objeto COutputPort es el responsable de enviar datos al dispositivo y de descargar datos en el puerto. Los últimos objetos gestionan los sonidos descargables que serán comentados en el siguiente paso. Ahora es posible comenzar a llamar a los métodos y activar todo el sistema MIDI. A continuación se explica como conseguir este propósito: try { // Inicializa DirectMusic CDMusic.Initialize(); // Inicializa los puertos dado un objeto gestor de DirectMusic COutPort.Initialize(CDMusic); CInPort.Initialize(CDMusic); El siguiente fragmento de código muestra cómo activar los puertos de entrada y salida: // Etructura de información para el puerto INFOPORT PortInfo; DWORD dwPortCount = 0; //Selección del sintetizador software do COutPort.GetPortInfo(++dwPortCount,&PortInfo); while (!(PortInfo.dwFlags & DMUS_PC_SOFTWARESYNTH)); COutPort.SetPortParams(0,0,1,SET_REVERB | SET_CHORUS,44100); COutPort.ActivatePort(&PortInfo,32); // Activación del puerto de salida a partir de la estructura de información cout << "\nPuerto de salida seleccionado: " << PortInfo.szPortDescription << endl; // Activación del puerto de entrada, selecciona el primero (por defecto) CInPort.GetPortInfo(1,&PortInfo); CInPort.ActivatePort(&PortInfo,SYSTEM_EXCLUSIVE_MEM); // Activa el objeto receptor CInPort.SetReceiver(Receiver); Las primeras líneas enumeran todos los puertos de salida y seleccionan el primer sintetizador software existente en el sistema, dado un número desde 1 hasta el valor de COutputPort::GetNumPorts en el primer parámetro de COutputPort::GetPortInfo. 110 C A P Í T U L O 5 • Programación MIDI Antes de llamar a COutputPort::ActivatePort necesitamos llamar al método COutputPort::SetPortParams para indicar el tipo de características que se necesitan en el puerto de salida (si se pasa el valor cero como parámetro, se asumirá la configuración por defecto para ese parámetro). Posteriormente se llamará a la función COutputPort::ActivatePort pasando un puntero a una estructura INFOPORT para activar el puerto de salida, usando los parámetros del número de grupo de canales y una frecuencia de muestreo pasada en la llamada a COutputPort::SetPortParams. El parámetro de canal de grupo es el número de grupos de canales MIDI para ser utilizados en el puerto software, cada grupo de canal es un conjunto de 16 canales MIDI. Uno de los parámetros configurables más importantes en el método COutputPort::SetPortParams es el parámetro de la frecuencia de muestreo, que es la frecuencia en Hertzios que se necesita para establecer la calidad del sonido en el puerto de salida. En este caso se asumen 44100 Hz como frecuencia de muestreo. En las últimas tres líneas se selecciona el puerto de entrada MIDI para la captura de mensajes, realizando operaciones similares. En este caso no se lleva a cabo ninguna enumeración, se limita a seleccionar el primer puerto de entrada por defecto. Es importante advertir que hay un segundo parámetro en el método CInputPort::ActivatePort que indica el tamaño de memoria máximo reservado para albergar datos en formato de sistema exclusivo. En este caso sólo se reservan 46.8 Kilobytes. Si se deja este parámetro opcional, el valor por defecto será 32 bytes, suficiente espacio para recibir datos MIDI estructurados estándar. Finalmente, se establece un objeto receiver por medio de una llamada al método CInputPort::SetReceiver. Si cerramos la llave de sentencia principal y se ejecuta la aplicación se obtendrá la siguiente salida: Figura 5.4 5.3.3.5 Quinto paso: comenzar la captura de música Capturar datos musicales desde un teclado externo es muy simple tan pronto como se haya inicializado el puerto de entrada. Si reservamos espacio para recibir datos en sistema exclusivo en la llamada a CInputPort::ActivatePort, ahora la aplicación estará preparada para manejar todos los eventos de entrada generados por un teclado controlador o un sintetizador. El siguiente código explica como realizar la captura: //Activa la recepción de mensajes de entrada CInPort.ActivateNotification(); cout << "Notificacion activada" << endl; // Redirige mensajes del canal global 0 al canal de destino global 0 CInPort.SetThru(0,0,0,COutPort); Como podemos observar, la primera línea de código activa la notificación de todos los mensajes de entrada MIDI usando un manejador de eventos que invoca a su respectiva función miembro virtual ya sobrecargada en la primera parte de la aplicación. 111 C A P Í T U L O 5 • Programación MIDI Otra característica de DirectMIDI es la redirección. Usando la redirección (MIDI thru) se pueden pasar mensajes MIDI desde un puerto de entrada activado a otro puerto de salida especificando un canal de grupo y el canal global de origen y de destino donde los mensajes serán redireccionados. La siguiente salida muestra un volcado de datos en sistema exclusivo y una captura de datos MIDI estándar estructurados: Figura 5.5 5.3.3.6 Sexto paso: aumentando el límite de instrumentos DirectMIDI soporta la carga de múltiples sonidos almacenados en ficheros DLS (Downloadable sound files). Esta tecnología es el estándar de los fabricantes MIDI para un formato de fuentes de sonido en el estado del arte de la tecnología multimedia. El actual formato DLS2 especifica todas las definiciones de los instrumentos: muestras, LFOs, filtros paso bajo, bucles y generadores de evolvente que serán descargados y renderizados en un puerto que soporte estos requisitos. DirectMIDI soporta dos tipos de operaciones DLS: DLS de alto nivel y DLS de bajo nivel. Ambas prestaciones se comentan a continuación. El DLS de alto nivel es la forma de manejar instrumentos de formas de onda que pueden ser almacenados en formatos DLS 1.0 y DLS 2.0. Estos ficheros pueden ser creados por una aplicación como DirectMusic Producer que permite configurar visualmente un amplio rango de parámetros antes mencionados. El DLS de bajo nivel permite descarga directa de bytes de datos en formato DLS 1.0 a un puerto sintetizador, proporcionando parámetros de articulación y regiones para el instrumento antes de la decarga. DLS de alto nivel: Utilizar ficheros DLS (.dls) en una aplicación DirectMIDI resulta flexible y sencillo. Para este propósito debemos declarar un objeto del tipo CDLSLoader con el fin de realizar la carga y la descarga de ficheros a la librería. Además de este objeto, es necesario declarar otro objeto del tipo CCollection que es un contenedor que permite albergar objetos del tipo Cinstrument, que son la instancia última de mantener una referencia al instrumento DLS. El código mostrado a continuación explica como llevar a cabo esta tarea: // Inicializa el objeto loader CLoader.Initialize(); // Carga el primer fichero DLS CLoader.LoadDLS(_T(".\\media\\sample.dls"),CCollectionA); // Carga la colección GM/GS por defecto del sintetizador software 112 C A P Í T U L O 5 • Programación MIDI CLoader.LoadDLS(NULL,CCollectionB); // Estructura de información de un instrumento INSTRUMENTINFO InstInfo; DWORD dwInstIndex = 0; // Enumera los instrumentos en la colección B while (CCollectionB.EnumInstrument(dwInstIndex++,&InstInfo) == S_OK) { cout << "Nombre del instrumento: " << InstInfo.szInstName << endl; cout << "Patch en coleccion: " << InstInfo.dwPatchInCollection << endl; cout << "----------------------------------------" << endl; } // Obtiene el instrumento con índice 214 de la colección B CCollectionB.GetInstrument(CInstrument1,214); // Lo asigna al programa MIDI 0 CInstrument1.SetPatch(0); cout << "\nInstrumento seleccionado: " << CInstrument1.m_strInstName << endl; cout << "Patch en la coleccion fuente " << CInstrument1.m_dwPatchInCollection << " al programa destino MIDI: " << CInstrument1.m_dwPatchInMidi << endl; // Obtien el instrumento con indice 0 de la coleccion A CCollectionA.GetInstrument(CInstrument2,0); // Lo asigna al programa MIDI 1 CInstrument2.SetPatch(1); cout << "\nInstrumento seleccionado: " << CInstrument2.m_strInstName << endl; cout << "Patch en la coleccion fuente " << CInstrument2.m_dwPatchInCollection << " al programa destino MIDI: " << CInstrument2.m_dwPatchInMidi << endl; // Establece el rango de notas CInstrument1.SetNoteRange(0,127); CInstrument2.SetNoteRange(0,127); // Descarga los instrumentos a los puertos de salida COutPort.DownloadInstrument(CInstrument1); COutPort.DownloadInstrument(CInstrument2); 113 C A P Í T U L O 5 • Programación MIDI cout << "Tocando el instrumento 1:" << CInstrument1.m_strInstName << endl; cout << "Presione una tecla para reproducir el segundo instrumento..." << endl; COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(PATCH_CHANGE,0,0,0),0) ; COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(NOTE_ON,0,40,127),0); getch(); COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(NOTE_OFF,0,40,0),0); COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(PATCH_CHANGE,0,1,0),0) ; COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(NOTE_ON,0,60,127),0); cout << "Tocando el instrumento 2:" << CInstrument2.m_strInstName << endl; getch(); COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(NOTE_OFF,0,60,0),0); La primera línea inicializa el objeto Loader que llama internamente a la función Win32 CoCreateInstance e instancia el objeto COM en el espacio del proceso. Una vez iniciado el objeto COM IDirectMusicLoader podemos proceder a la carga del fichero DLS usando el método CDLSLoader::LoadDLS, que toma una cadena de texto terminada en null representando un fichero, y una referencia a un objeto CCollection de destino donde los instrumentos definidos en el fichero serán almacenados en memoria. Cuando la cadena de texto proporcionada es null, DirectMIDI cargará el el conjunto estándar GM/GS definido en la EPROM del sintetizador. Para averiguar qué instrumentos residen en el objeto CCollection debemos llamar al método CCollectionEnumInstrument que toma una variable contador indicando el índice deseado del instrumento en la colección y un puntero a una estructura INSTRUMENTINFO que recibirá la información del instrumento en cuestión, es decir, el nombre del instrumento y el patch en la colección. Es posible también obtener una referencia particular a un instrumento llamando a la función sobrecargada CCollection::GetInstrument y proporcionando una referencia a un objeto CInstrument con el índice en la colección. Este método inciará las propiedades del objeto CInstrument con los datos del instrumento. El método CInstrument::SetNoteRange activa la región del teclado que debe responder a un evento note-on. Finalmente, es necesario proporcionar un programa MIDI destino para el instrumento en el sintetizador, llamando a la función miembro COutputPort::DownloadInstrument y pasando una referencia al objeto CInstrument. 114 C A P Í T U L O 5 • Programación MIDI La siguiente imagen muestra la salida del programa que se ha comentado previamente: Figura 5.6 DLS de bajo nivel: DirectMIDI 2.3 permite a una aplicación comunicarse directamente con un puerto que soporta descarga DLS. Hay dos alternativas para descargar datos al puerto: la primera es cargar un fichero de forma de onda PCM en formato WAV que contenga los datos de la muestra, y la segunda es generar la forma de onda en memoria usando funciones matemáticas. En el primer caso, es necesario cargar el fichero WAV mediante el método estático CDLSLoader::LoadWaveFile y proporcionar los siguientes tres parámetros: un puntero a una cadena terminada en null con la ruta del fichero en disco, una referencia a un objeto CSampleInstrument y un flag indicando el acceso deseado al fichero. Si el flag es la constante DM_LOAD_FROM_FILE, el fichero es siempre leído de disco cuando sea necesario; esto es útil para ficheros muy grandes. Si el flag es DM_USE_MEMORY, el fichero permanece almacenado en memoria dinámica aumentando la velocidad de acceso. Respecto a la segunda alternativa antes comentada, puede ser utilizada mediante el método CSampleInstrument::SetWaveForm, pasando un puntero a un buffer de bytes y proporcionando una estructura WAVEFORMATEX conteniendo el formato de la forma de onda. La forma de onda, tanto si se ha leído de un fichero WAV como si se ha generado matemáticamente, debemos almacenarla en un objeto CSampleInstrument. El código a continuación explica estas características: // Carga el fichero WAV CDLSLoader::LoadWaveFile(_T(".\\media\\starbreeze.wav"),CSample1,DM_US E_MEMORY); // Lo asigna a un patch CSample1.SetPatch(2); // Establece un loop contínuo CSample1.SetLoop(TRUE); // Establece parámetros WAV adicionales CSample1.SetWaveParams(0,0,68,F_WSMP_NO_TRUNCATION); REGION region; ARTICPARAMS articparams; 115 C A P Í T U L O 5 • Programación MIDI // Inicializa estructuras ZeroMemory(®ion,sizeof(REGION)); ZeroMemory(&articparams,sizeof(ARTICPARAMS)); // Establece los parámetros de región region.RangeKey.usHigh = 127; region.RangeKey.usLow = 0; region.RangeVelocity.usHigh = 127; // Ajusta LFO articparams.LFO.tcDelay = TimeCents(10.0); articparams.LFO.pcFrequency = PitchCents(5.0); // Establece el envolvente de Pitch articparams.PitchEG.tcAttack articparams.PitchEG.tcDecay articparams.PitchEG.ptSustain articparams.PitchEG.tcRelease = = = = TimeCents(0.0); TimeCents(0.0); PercentUnits(0.0); TimeCents(0.0); // Establece la envolvente de volumen articparams.VolEG.tcAttack articparams.VolEG.tcDecay articparams.VolEG.ptSustain articparams.VolEG.tcRelease = = = = TimeCents(1.275); TimeCents(0.0); PercentUnits(100.0); TimeCents(10.157); // Establece los parámetros de instrumentos CSample1.SetRegion(®ion); CSample1.SetArticulationParams(&articparams); // Reserva memoria para los sampes COutPort.AllocateMemory(CSample1); // Descarga el instrumento al puerto COutPort.DownloadInstrument(CSample1); COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(PATCH_CHANGE,0,2,0),0) ; cout << "Preparado para reproducir el fichero WAV" << endl; getch(); // Asigna patch 3 CSample2.SetPatch(3); // Establece parámetros adicionales CSample2.SetWaveParams(0,0,68,F_WSMP_NO_TRUNCATION); 116 C A P Í T U L O 5 • Programación MIDI // Establece los parámetros de los instrumentos CSample2.SetLoop(TRUE); CSample2.SetRegion(®ion); CSample2.SetArticulationParams(&articparams); // Genera los datos de la forma de onda // Muestras por segundo DWORD nSamplesPerSec = 44100; // La duración de la muestra double nTimeSec = 1.5; // Numero de muestras DWORD nSamples = static_cast<DWORD>(nTimeSec * nSamplesPerSec); // Frecuencia digital de la forma de onda double Frequency = 1000.0/nSamplesPerSec; // Reserva memoria para la forma de onda WORD *pRawData = new WORD[nSamples]; // Genera la forma de onda for(DWORD ni = 0;ni < nSamples;ni++) pRawData[ni] = static_cast<WORD>(30000*sin(2.0*PI*Frequency*ni) + 5000*sin(6.0*PI*Frequency*ni) + 1000*sin(10.0*PI*Frequency*ni)); // Formato de la forma de onda WAVEFORMATEX pwfex = {WAVE_FORMAT_PCM,1,44100,44100,2,16,0}; // Establece el formato de la forma de onda CSample2.SetWaveForm((BYTE*)pRawData,&pwfex,nSamples*2); // Reserva memoria para la interfaz COutPort.AllocateMemory(CSample2); //Descarga instruemento al puerto COutPort.DownloadInstrument(CSample2); COutPort.SendMidiMsg(COutputPort::EncodeMidiMsg(PATCH_CHANGE,0,3,0),0) ; La imagen 5.7 muestra la forma de onda generada en el ejemplo anterior: 117 C A P Í T U L O 5 • Programación MIDI Figura 5.7 5.3.3.7 Séptimo paso: terminar la aplicación El último paso de esta secuencia es cerrar la aplicación de una forma apropiada y limpia. Un ejemplo de esta función de limpieza y terminación de notificaciones puede verse en el código siguiente: // Finaliza la aplicaicón // Rompe la redirección CInPort.BreakThru(0,0,0); // Finaliza la notificación CInPort.TerminateNotification(); cout << "\n\nNotificacion terminada" << endl; cout << "Descargando instrumentos" << endl; // Descarga las colecciones del objeto Loader CLoader.UnloadCollection(CCollectionA); CLoader.UnloadCollection(CCollectionB); // Descarga los instrumentos del puerto cout << "Unloading instruments" << endl; COutPort.UnloadInstrument(CInstrument1); COutPort.UnloadInstrument(CInstrument2); // Descarga las muestras de los puertos COutPort.UnloadInstrument(CSample1); COutPort.UnloadInstrument(CSample2); // Libera la memoria reservada COutPort.DeallocateMemory(CSample1); COutPort.DeallocateMemory(CSample2); delete [] pRawData; cout << "Aplicación terminada...OK" << endl; 118 C A P Í T U L O 5 • Programación MIDI getch(); } catch (CDMusicException& DMExcp) { cout << "\n" << DMExcp.GetErrorDescription() << "\n" << endl; getch(); } return 0; } Si se activó la notificación en el puerto de entrada MIDI para recibir mensajes de entrada MIDI, es responsabilidad del programador llamar al método CInputPort::TerminateNotification para finalizar la recepción de eventos por este puerto. También es necesario llamar al método CInputPort::BreakThru si se estableció un conexión Thru entre dos puertos. Es importante liberar la memoria reservada por las colecciones de instrumentos. Para conseguir esto, debemos llamar al método CDLSLoader::UnloadCollection y liberar la interfaces DirectMusic internas llamando a COutputPort::DeallocateMemory. Esta operación hay que repetirla con los instrumentos descargados en el sintetizador mediante la llamada al método COutputPort::UnloadInstrument. Aunque la librería DirectMIDI liberará la memoria automáticamente, mediante el sistema de recolección de basura implementado en los destructores de las clases, es importante que el programador también se ocupe de estas cuestiones. 5.3.4 Manejo de excepciones DirectMIDI contiene la clase CDMusicException que maneja todas las posibles situaciones anómalas que ocurren durante la ejecución de la aplicación, permitiendo liberar al programador de comprobar constantemente los valores de retorno de las funciones DirectX mediante la macro FAILED. Básicamente el objeto provee tres importantes propiedades para informar sobre el error que son: m_hrCode que informa sobre el valor HRESULT de COM en DirectX, m_strMethod que proporciona la descripción del método donde la llamada a la función falló, y por último, m_nLine que devuelve el número de línea del código fuente del módulo donde se produjo el error. Además de estas tres propiedades, hay un método adicional para facilitar la descripción textual del error del método, CDMusicException::GetErrorDescription que retorna una cadena LPCTSTR conteniendo una descripción detallada del error una vez que la excepción a sido capturada. Para terminar, en la siguiente imagen se muestra un ejemplo que informa de un error durante la ejecución: Figura 5.8 Información de una excepción 119 C A P Í T U L O 5 • Programación MIDI Bibliografía y referencias La Web DirectMIDI wrapper class library http://directmidi.sourceforge.net/ MSDN http://msdn2.microsoft.com/es-es/default.aspx 120 Parte IV Reproducción de formatos de sonido C A P Í T U L O 6 • Reproducción de formatos de sonido 6 Reproducción de formatos de sonido 6.1 Introducción Para desarrollar aplicaciones profesionales y complejas se hace necesario el uso de abstracción y modularidad de las funciones de bajo nivel ya vistas en los capítulos 2 y 3 de la parte 2. Evidentemente, si requerimos implementar funcionalidades como mezclar gran cantidad de sonidos o reproducir ficheros de audio conocidos como el formato mp3, ogg, MIDI, WAV, etc. se nos presenta el problema de necesitar encapsular y abstraer la complejidad de las funciones COM de DirectX o de la cantidad de funciones de OpenAL, puesto que si se implementa en la misma capa que la lógica de negocio de nuestra aplicación, el resultado será desastroso. En el caso de desarrollar una aplicación de reproducción de ficheros mp3, sería muy confuso tener que mezclar el código de procesamiento y decodificación MPEG con las funciones de DirectSound. Esto también sería aplicable a un videojuego, donde el código y los objetos que gestionan el motor del juego no podrían entremezclarse con código de bajo nivel para reproducir un efecto o poner una música de fondo. La solución en este caso tendría dos alternativas: 1. Desarrollar por nuestra cuenta una capa software donde se implementara el código de acceso a los dispositivos de audio(DirectSound, OpenAL…), y, adicionalmente otra capa superior a ésta donde se implementaría la lógica más cercana al usuario como: reproducción de formatos de sonido conocidos y objetos de alto nivel para gestionar streams, sonidos cortos, mezclas, efectos, etc. o 2. Utilizar una librería de software de terceros donde ya se nos provea de estas funcionalidades antes descritas. La ventaja de este enfoque es la eficiencia de la reutilización y el ahorro del tiempo de desarrollo, puesto que esta parte puede ser subcontratada a terceras personas. La desventaja, sin embargo, de este enfoque es el coste de la adquisición de la licencia para la distribución al público. Afortunadamente, en el entorno del software libre existen soluciones muy aceptables y eficientes que resuelven este problema. En este capítulo se explicará la utilización de una librería GNU de software libre para resolver todos los problemas de una aplicación de audio profesional. Esta librería se llama Audiere que se explicará a continuación. 123 C A P Í T U L O 6 • Reproducción de formatos de sonido 6.2 La librería Audiere 6.2.1 Introducción Audiere es una librería software de audio con una API de alto nivel desarrollada por Chad Austin (jefe de desarrollo), Matt Campbell, Jacky Chong, Theo Reed, Richard SCAF y Ben Scoot como contribuidores. Entre las características más relevantes caben destacar: • • • • • • • API fácil e intuitiva. Permite la reproducción de audio en streaming y almacenado dinámicamente en buffers. Efectos de cambio de volumen, pan y velocidad de reproducción. Posibilidad de generación de formas de onda básicas como ruido blanco y ondas cuadradas. Enumeración en tiempo de ejecución de dispositivos de audio y formatos de ficheros de audio soportados. Manejo de ficheros por streams. Portabilidad a lenguajes como Python, Delphi, Java y XPCOM (JavaScript en Mozilla). Los formatos de audio que soporta son: • • • • • • Ogg Vorbis (OGG). MP3 (MPEG Layer-3) FLAC WAV descomprimido AIFF MOD, S3M, XM e IT. Las tecnologías software de salida de audio que soporta Audiere son: • • • • DirectSound WinMM (Windows Multimedia API) OSS en Linux y Cygwin SGI AL en IRIX Audiere es código fuente abierto y publicado bajo la licencia LGPL. Esto significa que es posible usar esta librería en productos comerciales siempre y cuando no se modifique el código fuente. En el caso de modificar el código fuente y liberar una nueva librería, ésta debe ser publicada bajo los términos de LGPL también. Audiere es una librería portable que ha sido testeada en sistemas como Windows, Linux i386, Cygwin e IRIX con al menos tres grandes compiladores. La arquitectura de Audiere es independiente de la codificación big-endian y little-endian. 124 C A P Í T U L O 6 • Reproducción de formatos de sonido La librería puede descargarse del repositorio de software libre SourceForge en la URL: http://audiere.sourceforge.net/home.php y en la sección download podemos descargar la librería completa cuya última versión es la 1.9.4. Una vez instalada la librería podemos acceder al programa de prueba en el directorio audiere-1.9.4-win32\bin y ejecutar la aplicación wxPlayer.exe. Veremos una interfaz de usuario como la siguiente: Figura 6.1 Aplicación de ejemplo de la librería Donde podremos ejecutar las funcionalidades básicas de la librería. En el ejemplo de la imagen 6.1 se están reproduciendo dos ficheros mp3 mezclados en tiempo real: uno en modo stream y otro en un buffer. Lógicamente el fichero mp3 cargado en forma de stream requiere menos tiempo de carga y memoria que el almacenado en el buffer puesto que lo reproduce bajo demanda en tiempo de ejecución. 125 C A P Í T U L O 6 • Reproducción de formatos de sonido 6.2.2 Clase AudioDevice Comenzaremos a programar una aplicación real de audio con la librería Audiere importando en primer lugar la cabecera audiere.h, como se muestra en el código siguiente: include #include #include #include <windows.h> <conio.h> <iostream> "audiere.h" using namespace audiere; using namespace std; int main(int argc, char* argv[]) { // Crea el dispositivo defecto para salida de audio AudioDevicePtr device(OpenDevice()); if (!device) { cout << "Error al crear dispositivo" << endl; return 1; } Como podemos observar en el código anterior, además de incluir la cabecera audiere.h y usar su espacio de nombres(namespace), se ha creado el objeto AudioDevice que maneja la gestión del dispositivo de audio de salida. La función OpenDevice permite inicilializar la librería, detectar y abrir el dispositivo de sonido por defecto. La clase AudioDevice deriva de la clase RefCounted como se ilustra en la siguiente figura: Figura 6.2 Esta clase base (RefCounted) permite contar referencias de instancias de la clase, así cuando el contador de referencias llega a cero se libera completamente la memoria que ocupaba el objeto AudioDevice. El objeto AudioDevice representa un dispositivo en el sistema que es capaz de abrir y mezclar varias fuentes de sonido. En Windows, DirectSound sería un ejemplo de un dispositivo. 126 C A P Í T U L O 6 • Reproducción de formatos de sonido 6.2.3 Clase OutputStream La clase OutputStream representa una conexión a un dispositivo de audio. De esta forma múltiples streams de salida son mezclados por un dispositivo de salida para producir una forma de onda final que es lo que el usuario oye. Cada objeto OutputStream puede ser reproducido o detenido independientemente de otros objetos OutputStream. Además se les puede establecer parámetros como volumen, pan y efectos también de forma independiente. Figura 6.3 Como en el caso anterior, esta clase también hereda de la clase RefCounted para la gestión de memoria. Sus métodos públicos disponibles al desarrollador son los que a continuación se muestran: virtual virtual virtual virtual virtual virtual virtual virtual virtual virtual virtual virtual virtual virtual virtual virtual void void bool void void bool void float void float void float bool int void int play ()=0 stop ()=0 isPlaying ()=0 reset ()=0 setRepeat (bool repeat)=0 getRepeat ()=0 setVolume (float volume)=0 getVolume ()=0 setPan (float pan)=0 getPan ()=0 setPitchShift (float shift)=0 getPitchShift ()=0 isSeekable ()=0 getLength ()=0 setPosition (int position)=0 getPosition ()=0 Como podemos apreciar, este objeto contiene los métodos principales para manejar las funcionalidades básicas de un sonido: cambiar el volumen, pan, reproducir, pausar, obtener la posición de la secuencia, cambiar la frecuencia de reproducción, etc. Si a continuación requerimos cargar un fichero en un objeto OutputStream para su reproducción tendremos que recurrir a la función global OpenSound que detectará automáticamente el tipo de formato y lo cargará en el objeto como stream o buffer según se le indique en el último parámetro mediante los flags true o false. En el código siguiente se muestra esta situación: 127 C A P Í T U L O 6 • Reproducción de formatos de sonido // Carga el fichero de sonido 3 OutputStreamPtr sound3(OpenSound(device, "..\\media\\sonido3.wav", false)); if (!sound3) { cout << "Error al cargar sonido3" << endl; return 1; } // Carga la música OutputStreamPtr stream(OpenSound(device, "..\\media\\OMD Electricity.mp3", true)); if (!stream) { cout << "Error al cargar musica" << endl; return 1; } 6.2.4 Reproducción y efectos Por último, una vez que se hayan cargado los ficheros en los objetos OutputStream, tan sólo queda ahora reproducir el sonido y aplicar efectos como pan, volumen y cambio de la velocidad de reproducción. Como se vio en la lista de métodos públicos de la clase OutputStream, tenemos todas las funciones necesarias para llevarlo a cabo, mediante, por ejemplo, el método play, setPan, setVolume, setRepeat, etc. En el código que se muestra en la siguientes líneas podemos apreciar el uso de estos métodos: while( condicion ) { switch( _getch() ) { // Reproduce buffer 1 case '1': sound1->play(); break; // Reproduce buffer 2 case '2': sound2->play(); break; // Reproduce buffer 3 case '3': sound3->play(); break; // Reproduce musica case '4': stream->play(); break; // Efecto pan de la musica case '5': for(float i= -1.0;i < 1.0;i+=0.1) { stream->setPan(i); // Izquierda->Derecha Sleep(100); } 128 C A P Í T U L O 6 • Reproducción de formatos de sonido for(float i= 1.0;i > -1.0;i-=0.1) { stream->setPan(i); // Derecha->Izquierda Sleep(100); } stream->setPan(0); // Centro break; // Frecuencia de reproduccion a 2x case '6': stream->setPitchShift(2.0); break; // Frecuencia de reproduccion a 0.5x case '7': stream->setPitchShift(0.5); break; // Frecuencia de reproduccion case '8': stream->setPitchShift(1.0); break; // Pausa musica case '9': stream->stop(); break; // Fin de la aplicacion case '0': condicion = false; } } Es fácil realizar estas operaciones. Como puede intuirse, todos los sonidos que se reproducen con el método play se mezclan sobre el mismo dispositivo, permitiendo utilizar un número indeterminado de fuentes de sonido. Esto puede ser útil para un videojuego donde varios efectos de sonido se deben mezclar con una banda sonora de fondo. El método setPan toma valores desde -1.0 (balance a canal izquierdo) a 1.0 (balance a canal derecho), estando en el 0.0 el balance central. Por último, el método setPitchShift cosigue cambiar la velocidad de reproducción con valores de 2.0 (reproduce a doble velocidad), 0.5 (reproduce a la mitad de la velocidad) y 1.0 (reproduce normal). Es de destacar que esta librería tiene una pólitica de gestión de memoria muy avanzada y automática por lo que puede terminarse la apliacación sin necesidad de liberar los recursos manualmente. 6.2.5 Acceso a los datos PCM de un buffer Uno de los aspectos más importantes de una librería de audio es poder acceder a los datos de audio PCM contenidos en un buffer de sonido. Para poder realizar esta operación con Audiere es necesario cargar el sonido en un objeto SampleSurce que además de ser la fuente para datos PCM permite posicionar el sonido. En la siguiente imagen se muestra la estructura de esta clase: 129 C A P Í T U L O 6 • Reproducción de formatos de sonido Figura 6.4 Previo a la obtención de los datos PCM necesitamos utilizar la clase SampleBuffer que es un contenedor de muestras de sonido de sólo lectura que permite abrir streams de muestras e iterar sobre ellos. Este objeto es usado en la situación donde un sonido muy largo se carga una sola vez en memoria y se redirige en stream varias veces hacia el dispositivo. Para obtener el objeto que hemos mencionado anteriormente llamaremos a la función global CreateSampleBuffer. En la siguiente imagen se muestra la estructura de esta clase estándar: Figura 6.5 Por lo tanto el código para llevar esto a cabo es el siguiente: #include #include #include #include <windows.h> <conio.h> <iostream> "audiere.h" using namespace audiere; using namespace std; int main(int argc, char* argv[]) { AudioDevicePtr device(OpenDevice()); if (!device) { cout << "Error abriendo dispositivo" << endl; return 1; } // Cargamos el fichero de sonido mp3 en un objeto SampleSource SampleSourcePtr sampleSource = OpenSampleSource("..\\media\\OMD - Electricity.mp3"); if (!sampleSource) { cout << "Error al cargar fichero" << endl; return 1; } // Se obtiene un buffer de datos a partir del objeto SampleSource 130 C A P Í T U L O 6 • Reproducción de formatos de sonido SampleBufferPtr sampleBuffer1 = CreateSampleBuffer(sampleSource); if (!sampleBuffer1) { cout << "Error al crear objeto sampleBuffer" << endl; return 1; } Con las funciones miembro getFormat y getLength podemos obtener información del formato de la forma de onda y la longitud en frames. Con el objetivo de reservar memoria dinámica para albergar los datos de la forma de onda se hace preciso calcular el tamaño de la muestra en bytes. Para ello se recurrirá a la siguiente función: Tamaño buffer(bytes) = número de canales × número de frames × bits por muestra Finalmente se recurre a la función miembro SampleBuffer::getSamples para obtener un puntero a los bytes de la muestra. Con todo esto se puede ya reservar memoria y almacenar los datos PCM en ella mediante el siguiente código de ejemplo: int channel_count,sample_rate,frame_count; SampleFormat sample_format; // Se obtienen los datos de formato del buffer de audio sampleBuffer1->getFormat(channel_count,sample_rate,sample_format); // Se obtiene el número de frames frame_count = sampleBuffer1->getLength(); // Se calcula el tamaño de muestra en bytes DWORD sampleSize = channel_count * frame_count * GetSampleSize(sample_format); cout << "El tamano de la muestra a invertir es: " << sampleSize << " bytes" << endl; // Puntero al array dinámico donde almacenar los datos WORD *pRawData = new WORD[sampleSize / 2]; // Volcado de datos al array CopyMemory(pRawData,sampleBuffer1->getSamples(),sampleSize); El código que se muestra a continuación es un ejemplo de inversión de una forma de onda, en este caso de una canción, para reproducir en reverse o backward. Con esta finalidad se ha creado un array auxiliar en memoria dinámica donde se realizará el proceso de inversión, que como se muestra en la figura siguiente, consiste en intercambiar el orden de los últimos bytes para ser los primeros en el array de forma invertida. El motor reproducirá la muestra palabra por palabra, ya que el formato es de 16 bits por muestra. 131 C A P Í T U L O 6 • Reproducción de formatos de sonido Array origen en bytes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Array destino en bytes 19 20 17 18 15 16 13 14 11 12 9 10 7 8 5 6 3 4 1 2 Figura 6.6 Así, por lo tanto, el algoritmo que lleva a cabo esta inversión es el siguiente: // Array auxiliar para la inversión WORD *pRawAux = new WORD[sampleSize / 2]; // Indice al penúltimo byte de datos int t = sampleSize / 2 - 1; // Proceso de inversión for(int i = 0;i < sampleSize / 2;i++) { pRawAux[i] = pRawData[t]; t--; } Ya sólo falta crear un nuevo objeto SampleBuffer a partir del nuevo array con los datos invertidos y de ahí obtener un objeto SampleSource mediante el método SampleBuffer::openStream. Por último se pasa este objeto a la función global ya comentada, OpenSound, y se provee un puntero al objeto SampleBuffer que contiene la muestra invertida. Esta función nos devolverá definitivamente un puntero a un objeto OutputStream cuyo destino final es poder ser reproducido. El código que se muestra a continuación ilustra estas operaciones: // Crear un nuevo objeto SampleBuffer para albergar datos SampleBufferPtr sampleBuffer2 = CreateSampleBuffer(pRawAux,frame_count,channel_count,sample_rate,sampl e_format); if (!sampleBuffer2) { cout << "Error al crear objeto SampleBuffer" << endl; return 1; } // Se obtiene un puntero al objeto SampleSource SampleSourcePtr sampleSource2 = sampleBuffer2->openStream(); // Se libera la memoria de los arrays de datos delete [] pRawData; delete [] pRawAux; // Se crea un objeto de salido de sonido stream OutputStreamPtr sound2(OpenSound(device,sampleSource2,false)); if (!sound2) { cout << "Error al crear el objeto OutputStream" << endl; return 1; 132 C A P Í T U L O 6 • Reproducción de formatos de sonido } // Finalmente se reproducen los datos invertidos cout << "Reproduciendo sonido invertido. Pulse una tecla para finalizar..." << endl; sound2->play(); _getch(); return 0; } 6.3 Formatos de sonido conocidos 6.3.1 Formato WAV El formato WAV o WAVE cuyo nombre proviene de waveform audio file format, es un formato de audio sin compresión desarrollado por Microsoft e IBM. Este formato admite los modos mono y estéreo a diversos bits y velocidades de muestreo. Se encuentra normalmente en los sistemas PC con la extensión .wav y es el principal de los sistemas operativos Windows. El formato WAV deriva del formato RIFF (Resource Interchange File Format) y es relativamente parecido al los formatos AIFF e IFF utilizados en sistemas Mac OS. Una de las ventajas que proporciona el formato es que puede soportar cualquier tipo de códec; aunque principalmente se utiliza con el formato PCM sin comprimir. Una de las limitaciones de WAV es que sólo se puede grabar un archivo de hasta 4 gigabytes, debido a una limitación en la cabecera del fichero donde se indica la longitud del mismo con un número de 32 bits. La estructura de este fichero es muy simple, con una cabecera compuesta por 4 parámetros: duración temporal, frecuencia de muestreo, resolución y grabación mono o estéreo. 6.3.2 Formato AIFF Este formato de audio fue desarrollado por Apple en 1998 y está basado en el formato IFF. Es le acrónimo de Apple Interchange File Format y es uno de los formatos de sonido más compatibles. Los datos de audio en estándar AIFF no están comprimidos, almacenándose los datos en big-endian y codificación PCM. Este formato tiene una variante con compresión que se le denomina AIFF-C o AIFC, con soporte para una gran variedad de códecs. El estándar AIFF es uno de los formatos líderes, junto a WAV, usados a nivel profesional para aplicaciones de audio, ya que, a diferencia del formato MP3, está comprimido sin ninguna pérdida lo que facilita un rápido procesamiento de señal en detrimento del aumento de espacio en memoria secundaria. Una de las ventajas de este estándar es poder dar soporte a bucles en notas musicales para su uso en samplers. El formato se puede encontrar con extensiones .aiff o .aif, y para las variantes comprimidas con extensión .aifc, aunque las anteriores también admiten compresión. 133 C A P Í T U L O 6 • Reproducción de formatos de sonido 6.3.3 Formatos MPEG: MP3 El formato MPEG-1 Audio Layer III o MPEG-2 Audio Leyer III, más comúnmente conocido MP3, es un formato de compresión de audio digital patentado que usa un algoritmo con pérdida para obtener tamaños de ficheros relativamente pequeños. MP3 fue desarrollado por el Moving Picture Experts Group (MPEG) que es una organización encargada de desarrollar normativas para compresión de vídeo y audio digital. MP3 forma parte del estándar MPEG-1 y del posterior y más extendido MPEG2. El estándar está recogido en las normativas ISO/IEC 11172-3 e ISO/IEC 13818-3. Estos tipos de ficheros que suelen encontrarse en ordenadores personales y reproductores de audio, tiene la extensión .mp3. Historia Este formato fue desarrollado principalmente por Karlheinz BrandenBurg, director de tecnologías del Instituto Fraunhofer IIS. Las primeras patentes fueron registradas en 1986 y varias más en 1991. Pero no fue hasta julio de 1995 cuando Brandenburg usó por primera vez la extensión .mp3 para los archivos de su ordenador. A partir de esta fecha el formato MP3 se convirtió en muy popular y fue el sistema utilizado para streaming de audio y compresión de alta calidad, aunque en los equipos de alta fidelidad era patente la pérdida de calidad acústica. Con un fichero MP3 con una compresión de 128kbits/s se conseguía un tamaño unas 11 veces menor que su homónimo en CD. Con el boom de Internet a principios del milenio el formato tuvo una gran difusión, ya que hizo posible el intercambio de ficheros musicales. De todos es sabido el problema actual con las copias piratas y la violación de los derechos de autor derivadas del desarrollo de software P2P. Actualmente MP3 goza de un gran éxito y su utilización está siendo cada vez más frecuente. Especificaciones técnicas El sistema de codificación MP3 utiliza un algoritmo con pérdidas. Sin embargo, las pérdidas corresponden a frecuencias que el oído humano no puede captar. El algoritmo elimina toda la información que no se necesita. Para ello utiliza un sistema denominado codificación de subbandas, proceso el cual la señal se descompone en subbandas a través del banco de filtros híbrido. Las subbandas obtenidas, se comparan con el original mediante un modelo psicoacústico que discrimina las bandas que no son importantes. Por último las subbandas son cuantizadas y codificadas y el resultado final se comprime con un algoritmo tipo Huffman LZW. En la capa III de MPEG-1, se encuentra el llamado banco de filtros híbrido. Esta mejora de la resolución frecuencial empeora la resolución temporal introduciendo problemas de pre-eco que son predichos y corregidos. Este sistema utilizado en la capa III es el llamado banco de filtros híbrido polifase/MDCT. Se encarga de realizar el mapeado de la señal en el dominio del tiempo 134 C A P Í T U L O 6 • Reproducción de formatos de sonido al de la frecuencia tanto para el codificador como para los filtros de reconstrucción del decodificador. Las muestras de salida del banco están cuantizadas y proporcionan una resolución en frecuencia variable, 6x32 o 18x32 subbandas, ajustándose mucho mejor a las bandas críticas de las diferentes frecuencias. La capa III tiene tres modos de bloque de funcionamiento: dos modos donde las 32 salidas del banco de filtros pueden pasar a través de las ventanas y las transformadas MDCT y un modo de bloque mixto donde las dos bandas de frecuencia más baja usan bloques largos y las 30 bandas superiores usan bloques cortos. Para la capa III de MPEG-1 se especifican cuatro tipos de ventana: 1. 2. 3. 4. Normal Transición de ventana larga a corta (Start) 3 ventanas cortas (Short) Transición de ventana corta a larga (Stop) La compresión de basa en la reducción del margen dinámico irrelevante, es decir, en la incapacidad del sistema auditivo para detectar los errores de cuantización en condiciones de enmascaramiento. Este estándar divide la señal en bandas de frecuencia que se aproximan a las bandas críticas, y luego cuantifica cada subbanda en función del umbral de detección del ruido dentro de esa banda. El modelo psicoacústico es una modificación del empleado en esquema II, y utiliza un método denominado predicción polinómica. Analiza la señal de audio y calcula la cantidad de ruido que se puede introducir en función de la frecuencia, es decir, calcula “la cantidad de enmascaramiento” o umbral de enmascaramiento en función de la frecuencia. El codificador utiliza esta información para decidir la mejor manera de gastar los bits disponibles. El estándar provee dos modelos piscoacústicos el I y el II. El modelo I es mucho menos complejo que el II y simplifica considerablemente los cálculos. Las pruebas acústicas, realizadas con personas con oído muy agudo, se han utilizado para comprobar si la codificación es suficientemente buena. En la tabla 6.1 pueden observarse las diferentes características de distintas fuentes de sonido. Capa 1 2 3 Compresión 4:1 6:1 a 8:1 10:1 a 12:1 Transferencia 384 kbits/s 256 a 192 kbits/s 128 a 112 kbits/s Tabla 6.1 Características de las distintas fuentes de sonido 135 C A P Í T U L O 6 • Reproducción de formatos de sonido En la tabla 6.2 se muestra las características de distintas fuentes de sonido. Calidad Teléfono Radio AM Radio FM CD DAT Muestreo 8 Khz 11,025 Khz 22,050 Khz 44,1 Khz 48 Khz Resolución 8 8 16 16 16 Modo Mono Mono Estéreo Estéreo Estéreo Tasa transferencia 64 kbps 88 kbps 705,6 kbps 1441,2 kbps 1536 kbps Tabla 6.2 Características de distintas fuentes de sonido Así por ejemplo para el oído no experimentado, con 128 kbps o hasta 192 kbps es aceptable. En personas que escuchan mucha música o que tienen experiencia en la parte auditiva, desde 192 a 256 kbps debe ser recomendable. En la música que circula por Internet las tasas suelen ser de 128 y 192 kbps. Para la codificación y la cuantización la solución que propone el estándar en cuanto a la repartición de bits o ruido, se hace en un ciclo de iteración que consiste en un ciclo interno y otro externo: Ciclo interno: realiza la cuantización no-uniforme de acuerdo con el sistema de punto flotante. El ciclo escoge un determinado intervalo de cuantización y, a los datos cuantizados, se les aplica codificación Huffman en el siguiente bloque. El ciclo termina cuando los valores cuantizados que han sido codificados con Huffman usan menor o igual número de bits que la máxima cantidad de bits permitida. Ciclo externo: este ciclo se encarga de verificar si el factor de escala para cada subbanda tiene más distorsión de la permitida (ruido en la señal codificada), comparando cada banda del factor de escala con los datos previamente calculados en el análisis psicoacústico. Por último falta empaquetar los datos. Para ello se toman muestras cuantificadas del banco de filtros, junto a los datos de asignación de bits/ruido y almacena el audio codificado y datos adicionales en tramas. Cada trama contiene hasta 1152 muestras de audio y está formada por un encabezado, junto con un chequeo de detección de errores CRC y datos auxiliares. 6.3.4 Formato General MIDI General MIDI conocido con el acrónimo GM exige que todos los instrumentos sean compatibles con el mismo estándar GM. El estándar especifica qué sonidos se crean con el ordenador y cómo van a ser enviados al procesador para que emita los sonidos, por tanto es la especificación GM la que define el formato MIDI en una tarjeta de sonido. 136 C A P Í T U L O 6 • Reproducción de formatos de sonido Los ficheros GM contienen las partituras de las composiciones musicales en formato MIDI. La extensión con la que se encuentra el formato suele es .mid, aunque existe una variante llamada .kar que contiene la lírica de las canciones para ser interpretadas por sistemas de “karaoke”. El conjunto GM consta de un total de 128 instrumentos clasificados por tipos como se muestra en el apéndice A3. Sin embargo, para la especificación GM2 se definen un total de 256 instrumentos y 9 kits de batería GS (General Sound). Estos ficheros ocupan muy poco espacio físico y las composiciones suelen ser reproducidas a 16 bits a 44,1 kHz en estéreo y efectos. Para reproducir un fichero MIDI no requiere los recursos que necesita un fichero de audio convencional, como por ejemplo un MP3 o un XM. La MMA (Asociación Internacional de fabricantes MIDI) publicó una normativa en cuanto a la estandarización de los archivos MIDI o SMF que consisten en la siguiente estructura: Cabecera: id tamano formato deltatime Identificador que contiene los caracteres MThh Tamaño de la longitud del fichero sin contar id ni tamano 0 – Pista única 1 – Multipista, síncrono (todas las pistas se ejecutan simultáneamente) 2 – Multipista asíncrono (las pistas se ejecutan de manera secuencial) Número de intervalos contenidos en ¼ de nota. Especifica tiempo de reproducción. Si quisiéramos definir una estructura en C++ para albergar estos datos podríamos recurrir a una clase estándar como la del siguiente ejemplo: class MIDIhdr{ private: char id[4]; long tamano; unsigned formato; unsigned numero_pistas; unsigned deltatime; public: void setid(const char *id); char *getid(); etc. }; 137 C A P Í T U L O 6 • Reproducción de formatos de sonido Segmentos de pista: Cada segmento de pista está compuesto por una cabecera, seguida de una serie de eventos MIDI. Entre estos eventos se encuentran los comandos, que son los datos enviados y recibidos por los puertos MIDI. Cadena de cuatro caracteres que identifica la cabecera del track. Es MTrk. Indica el tamaño de la pista sin indicar la cabecera, es decir, el número de comandos que contiene. id tamano En C++ podemos implementar simplemente con la clase estándar: class Miditrk { private: char long public: void char etc. id[4]; tamano; setid(const char *id); *getid(); }; Notas musicales: En MIDI están comprendidas con un numero decimal positivo entre 0 y 127. Son las siguientes: Octava 0 1 2 3 4 5 6 7 8 9 10 Do 0 12 24 36 48 60 72 84 96 108 120 Do# 1 13 25 37 49 61 73 85 97 109 121 Re 2 14 26 38 50 62 74 86 98 110 122 Re# 3 15 27 39 51 63 75 87 99 111 123 Mi 4 16 28 40 52 64 76 88 100 112 124 Fa 5 17 29 41 53 65 77 89 101 113 125 Fa# 6 18 30 42 54 66 78 90 102 114 126 Sol 7 19 31 43 55 67 79 91 103 115 127 Sol# 8 20 32 44 56 68 80 92 104 116 La 9 21 33 45 57 69 81 93 105 117 La# 10 22 34 46 58 70 82 94 106 118 Si 11 23 35 47 59 71 83 95 107 119 Valores de longitud variable: La finalidad de utilizar comandos con datos de longitud variable es representar un amplio rango de valores evitando que valores pequeños ocupen más espacio físico de lo necesitado. Estos valores se forman como una secuencia de bytes, del que sólo cuentan los siete bits menos significativos. El MSB debe ser siempre 1 si quedan más bytes para representar el valor y 0 para el último byte. 138 C A P Í T U L O 6 • Reproducción de formatos de sonido Eventos: Están formados por los comandos de voz y los comandos de modo. Los primeros constan de un byte de estado seguido de datos dependientes del byte de estado. Para los 16 posibles canales de GM-1 se utilizan los 4 bits más bajos del byte de estado. Así, por ejemplo, para indicar que una nota ha sido pulsada o liberada se utilizaría los siguientes bytes de estado: Byte de estado 0x8.... Significado Liberación de nota 0x9.... Pulsación de nota Datos 1 byte de nota + 1 byte de velocidad pulsación 1 byte de nota + 1 byte de velocidad pulsación Los comandos de modo constan sólo de un byte que indica el estado pero no requieren ningún canal determinado. El comando más utilizado tiene como función silenciar las notas en todos los canales. Mensajes de sistema exclusivo: Como se comentó en el capítulo 4, los mensajes de sistema exclusivo son datos no estructurados proporcionados por cada fabricante de sistema MIDI que tienen una nomenclatura determinada. Los mensajes de sistema exclusivo tienen el siguiente formato de arranque: 0xF0 + longitud + mensaje no estructurado Se pueden enviar desde un solo bloque hasta varios. Para el envío de un mensaje de múltiples bloques se hace necesario indicar el final del stream indicándolo con el byte 0xF7. Eventos META: Los eventos Meta no se consideran mensajes MIDI sino otro tipo de información que se asocia al fichero. Para desarrollar un componente de lectura de un fichero MIDI no es necesario conocer todos los eventos Meta. Por su extensión se ha eludido indicar en esta sección los diferentes eventos. Si el lector está interesado en conocer los diferentes eventos, se le anima a consultar el sitio Web de la MMA. Para reproducir un fichero MIDI en Audiere es necesario en primer lugar utilizar un nuevo tipo de dispositivo, llamado MIDIDevice que gestiona el objeto de control del puerto MIDI. En segundo lugar, para abrir el puerto MIDI se invocará a la función OpenMIDIDevice con el parámetro NULL, puesto que actualmente no se encuentra implementado en esta librería la elección de otros tipos de puertos. Debido a esto, sólo será posible seleccionar un puerto MIDI por defecto. En último lugar y con el fin de reproducir el fichero MIDI, se recurrirá al objeto MIDIStream que albergará el contenido del archivo previamente leído y almacenado 139 C A P Í T U L O 6 • Reproducción de formatos de sonido con el método MIDIDevice::openStream, pasándole como parámetro un cadena con la identificación del archivo. En la siguientes líneas de código podemos apreciar la utilización de estas clases. // Dispositivo MIDI MIDIDevicePtr midi(OpenMIDIDevice(NULL)); if (!midi) { cout << "Error al crear dispositivo MIDI" << endl; return 1; } MIDIStreamPtr midStream(midi->openStream("../media/SONG_001.mid")); if (!midStream) { cout << "Error al cargar fichero MIDI" << endl; return 1; } Por último invocaremos al método MIDIStream::play para reproducir el contenido del fichero MIDI, como se muestra en el ejemplo: // Reproduce fichero MIDI case 'x': midStream->play(); break; 140 C A P Í T U L O 6 • Reproducción de formatos de sonido 6.3.5 Formato MOD / XM El formato MOD tiene su origen en los ordenadores Amiga donde los programas de edición musical, llamados trackers, solían almacenarlos en ficheros con extensión .mod. La primera versión del formato fue creado por Karsten Obarski para su uso en el editor Ultimate Soundtracker; tracker publicado para el ordenador Amiga en 1987. Desde entonces, el formato ha sido soportado por cientos de reproductores de audio y docenas de trackers. En las primeras versiones del formato MOD se utilizaban sólo 4 canales para la reproducción simultánea, correspondiendo a las capacidades del chipset de audio original de Amiga, y un límite de 15 instrumentos. El formato fue diseñado para ser directamente reproducido en el Amiga sin procesamiento adicional por hardware. Por ejemplo, las muestras de sonido eran almacenadas en formato PCM de 8 bits de resolución que se enviaban posteriormente al DAC del sistema de audio. Tampoco los datos de las “patterns” u hojas de notas estaban empaquetados. La gran innovación de este sistema era el poco uso de CPU que requería para la reproducción de ficheros de audio. Esto fue utilizado para reproducir música de fondo en los videojuegos de la época. El fichero MOD tuvo su máximo auge en la llamada Demoscene (movimiento cultural para el arte infográfico en tiempo real) donde dos “demosceners”, llamados Mahoney y Kaktus, desarrollaron un papel relevante en el diseño del formato. Consideraciones técnicas: Un “pattern” es normalmente representado en una interfaz de usuario como una tabla con una columna por canal, así si tenemos 4 columnas; una para cada canal, cada columna tiene un total de 64 filas o “rows”. Una celda en la tabla pude causar un cambio en el canal cuando el tiempo de la fila (row) se ha alcanzado. Por ejemplo: Comienzo de la reproducción de una nueva nota, a un determinado volumen y efectos. Cambiar el volumen o efecto especial de la nota. Cambiar el flujo del “pattern” (saltar a una determinada posición dentro del “pattern” o hacer un loop en el mismo). Etc. Respecto al Timing, el mínimo tiempo per frame es 0.02 segundos, o un retrazo vertical (intervalo VSync), ya que el software original usaba la sincronización VSync del monitor at 50 Hz para sistemas PAL ó 60 Hz para sistemas NTSC. La frecuencia a la que un “pattern” es reproducido está definida por la configuración de velocidad. Cada fila o “row” en el “pattern” dura un retrazo vertical (0.02 segundos). La configuración de velocidad varía entonces entre 1 a 255. Con este 141 C A P Í T U L O 6 • Reproducción de formatos de sonido factor se consigue que el tempo de la melodía o canción se reproduzca más deprisa o más lentamente. En versiones posteriores del formato, el retrazo vertical fue reemplazado con un periodo de tiempo ajustable en el rango [0.01,0.078] segundos. Lamentablemente, algunas de las funcionalidades antiguas fallaron, puesto que el nuevo comando de establecimiento de la velocidad tenía un código idéntico al antiguo. Por ende, los valores entre [1, 31] eran interpretados como configuraciones antiguas, pero los demás valores fueron considerados modificaciones al período de tiempo ajustable. Como consecuencia, los valores comprendidos entre [32, 255] usados en algunas ficheros fallaban en las nuevas versiones de los trackers. Formato XM: Una variante del formato MOD que surgió posteriormente a su desarrollo fue el formato XM, acrónimo de “extended module”, e introducido por los “demosceners” que desarrollaron el secuenciador “Fast Tracker 2”. Su extensión es .xm. Entre las características técnicas introducidas por este formato, caben destacar: instrumentos multi-muestra con efectos de volumen, envolventes de panning y compresión de “patterns”. También aumentó el número de canales y de comandos utilizados por el antiguo fichero MOD, añadió soporte para muestra PCM de 16 – bits y ofreció una tabla de frecuencias alternativas para “portamentos”. El formato XM se utiliza actualmente en muchas composiciones para ordenador, sobre todo en “demos” y videojuegos. Figura 6.6 Imagen del mítico editor de MOD / XM FastTracker II 142 C A P Í T U L O 6 • Reproducción de formatos de sonido Bibliografía y referencias Libros Diseño y desarrollo Multimedia, Sistemas, Imagen, Sonido y Vídeo – Manuel-Alonso Castro Gil, Antonio Colmenar Santos, Pablo Losada de Dios, Juan Peire Arroba. Editorial Ra-ma, 2002. La Web Wikipedia: WAV http://es.wikipedia.org/wiki/Waveform_Audio_Format AIFF http://es.wikipedia.org/wiki/AIFF MP3 http://es.wikipedia.org/wiki/MP3 GM http://es.wikipedia.org/wiki/General_MIDI MOD http://en.wikipedia.org/wiki/MOD_(file_format) XM http://en.wikipedia.org/wiki/XM_(file_format) GM http://www.lpi.tel.uva.es/~nacho/docencia/ing_ond_1/trabajos_01_02/formatos_audio_ digital/html/midiformat.htm – Juan Ignacio Arribas Audiere documentation: http://audiere.sourceforge.net/documentation.php MSDN http://msdn2.microsoft.com/es-es/default.aspx 143 Parte V Tratamiento Digital de Señal C A P Í T U L O 7 • Tratamiento Digital de Señal 7 Tratamiento Digital de Señal 7.1 Procesado digital de señal aplicado al audio El procesado digital de señal ha supuesto un gran avance en el campo del audio, aunque su desarrollo ha sido relativamente lento por la gran cantidad de potencia de cálculo que se requiere. La primera aplicación comercial del procesado digital data de principios de los 80 en los reproductores de CD, que incorporaban filtros FIR para mejorar la reconstrucción de la señal analógica. En esta época todavía resultaba demasiado caro cualquier otro tipo de procesado digital, de manera que los soportes digitales convivían con amplificadores y ecualizadores analógicos. Incluso en los estudios de grabación se empleaba una amplia variedad de equipos analógicos por la escasez de equipos y aplicaciones capaces de procesar el audio digital. A principios de los 90 hizo su aparición el DSP (Digital Signal Processor), un procesador especializado para operaciones matemáticas en serie sobre grandes cantidades de datos. El DSP permitió llevar a la práctica toda la teoría de procesado digital que hasta entonces sólo podía ser probada en simulaciones, pero no en tiempo real. Además el DSP era programable, lo cual permitía cambiar sobre la marcha el tipo de procesado que se hacía sobre el audio. Hasta entonces la única manera económica de disponer de un procesado digital era mediante circuitos programados sobre hardware, como era el caso de los reproductores de CD. Al principio el DSP era caro, ya que debía tener suficiente potencia para realizar por software lo que otros circuitos hacían por hardware. Durante algunos años el procesado digital de audio dependía de costosas tarjetas DSP que se añadían a los ordenadores para realizar los cálculos que eran demasiado pesados para el procesador. Estas tarjetas eran además propietarias, de manera que el hardware y la aplicación estaban estrechamente relacionados. A mediados de los 90 comenzaron a aparecer procesadores de uso general con extensiones multimedia, es decir, con un DSP integrado. Progresivamente el juego de instrucciones multimedia se fue popularizando e integrándose en las aplicaciones, y comenzaron a aparecer los primeros programas de procesado de audio para todos los públicos. La consagración del procesado digital como norma general para el audio se produjo con la popularización del formato MP3. La capa III de MPEG es una especificación para la compresión de audio por transformada, que en lugar de almacenar la onda de audio en el tiempo se almacena su transformada en frecuencia, con ciertos recortes para reducir la tasa binaria. A partir de este momento el procesado digital saltó de los ordenadores a los dispositivos portátiles, resultando cada vez más barato y más eficiente. 147 C A P Í T U L O 7 • Tratamiento Digital de Señal 7.2 Algoritmos básicos para audio En esta sección se comentarán algunos algoritmos básicos en el procesado de audio. Existen muchos más efectos, pero casi todos se basan en combinaciones de los que aquí se mencionan. Los efectos de audio se pueden clasificar en dos grupos básicos: • Lineales: el audio resultante es una combinación de las muestras presentes o pasadas del audio inicial multiplicadas por coeficientes que son independientes de la amplitud. Esto no significa que los coeficientes tengan que ser constantes, sino que en un instante de tiempo dado se cumplan las propiedades de aditividad y homogeneidad respecto de la señal de entrada. En esta categoría se encuadran el filtrado, la ecualización, el mezclado, el retardo y la envolvente. • No lineales: el audio resultante depende del audio inicial con una relación que no es una constante, sino una función del mismo. En esta categoría se encuadran la compresión o expansión del rango dinámico, la distorsión y el recorte (clipping). Como veremos a continuación, muchos de los efectos que simulan procesos del mundo real están modificados en alguno de sus parámetros por funciones que varían con el tiempo. 7.2.1 Filtros Un filtro es un procesado de sonido que tiene como resultado la reducción o amplificación de la señal en alguna banda de frecuencia. El filtrado se consigue mediante la suma de la señal original con diversas muestras retardadas y multiplicadas por ciertos coeficientes. Existen dos categoría básicas para la realización de filtros digitales: • IIR (Infinite Impulse Response), o filtro recursivo: en este tipo de filtro la señal de salida depende de la señal de entrada con diversos retardos y de la señal de salida retardada, todas ellas multiplicadas por ciertos coeficientes. Al depender la señal de salida de las muestras retardadas de la misma, se produce un bucle de realimentación que mantiene la señal activa de manera infinita. En la práctica, los coeficientes y la cuantización de los datos hacen que la señal realimentada acabe extinguiéndose. También es posible que la señal de salida oscile o se amplifique, pero estos no son efectos deseables en un filtro. Un filtro IIR responde a la fórmula general: y[n] = b0·x[n] + b1·x[n-1] + b2·x[n-2] + ... – a1·y[n-1] – a2·y[n-2] – ... • 148 FIR (Finite Impulse Response), o filtro no recursivo: En este tipo de filtro la señal de salida depende únicamente de las muestras de la señal original con diversos retardos y multiplicadas por ciertos coeficientes. Por tanto, una vez extinguida la señal de entrada, la señal de salida desaparecerá una vez transcurrido el retardo del filtro. Una característica del filtro FIR es que la C A P Í T U L O 7 • Tratamiento Digital de Señal respuesta impulsional se corresponde con los valores de los coeficientes del filtro, lo cual simplifica bastante la programación de una respuesta en frecuencia determinada. Un filtro FIR responde a la fórmula general: y[n] = b0·x[n] + b1·x[n-1] + b2·x[n-2] + ... Para ambas categorías existen varias formas de construir el filtro según cómo se desee optimizar el hardware asociado. Los elementos de diseño básicos son el retardo (que es un registro), el multiplicador y el sumador. La estructura del filtro es muy importante para optimizar el uso de recursos y la precisión de las operaciones. Para una función de transferencia dada, existen diversas implementaciones posibles. Las estructuras básicas son: • Forma directa I: esta estructura requiere el doble de celdas de retardo que el orden del filtro. Figura 7.1 • Forma directa II o canónica: esta estructura requiere el mismo número de celdas de retardo que el orden del filtro. Figura 7.2 En el caso de un filtro FIR, la sección de coeficientes ai no existiría. Dicha sección representa la parte recursiva del filtro, y es responsable de los polos de la función de 149 C A P Í T U L O 7 • Tratamiento Digital de Señal transferencia. La sección de coeficientes bi representa los ceros de la función de transferencia. En la cercanía de los polos, la función de transferencia aumenta mucho de valor, pudiendo causar problemas de desbordamiento en los sumadores. En la cercanía de los ceros, la función de transferencia se acerca a cero, pudiendo causar problemas de cuantización. Es por ello que la elección de la estructura del filtro debe hacerse cuidadosamente según la función que se desee implementar. La ventaja de los filtros FIR es que se pueden realizar estructuras muy largas puesto que la señal no se amplifica durante el camino. No obstante, para lograr una respuesta abrupta se puede requerir una gran cantidad de etapas. Los coeficientes del filtro FIR equivalen a las muestras de la respuesta impulsional, o dicho de otra manera, a la transformada inversa de su respuesta en frecuencia, lo cual simplifica su realización. Los filtros IIR pueden presentar grandes resonancias a lo largo de la estructura. En consecuencia, se suele recurrir a encadenar filtros IIR de orden bajo para asegurar que la señal no diverja demasiado. El diseño del filtro IIR es más complejo, ya que hay que combinar ceros y polos para lograr la respuesta deseada. Una manera típica de diseñar filtros IIR es usando las funciones de transferencia de filtros analógicos como referencia. 7.2.2 Envolvente de volumen Una envolvente de volumen es una modulación de la amplitud de la señal de forma variable en el tiempo. Equivale a multiplicar el valor de cada muestra por el valor de la función envolvente en el correspondiente instante de tiempo. Las envolventes se emplean de manera sistemática en la síntesis de sonido para generar sonidos más reales. A continuación se describen algunos tipos de envolventes: • • • Fundido (fade): el fundido se suele utilizar al inicio de una pieza musical (fadein) o al final (fade-out) para lograr una transición suave de la música al silencio y viceversa. En estos casos se aplica una envolvente exponencial para que, combinada con la respuesta del oído al volumen, el oyente tenga una impresión lineal. Trémolo: el trémolo se utiliza para simular instrumentos musicales en la síntesis de sonido, y suele ser una envolvente sinusoidal de frecuencia inferior a la audible. Envolvente de nota musical: modula la amplitud de la nota en 4 fases: ataque, decaimiento, sostenido y liberación. Simula los distintos estados de vibración por los que pasa un instrumento musical durante la generación de una nota. En el caso de un instrumento de cuerda hay 4 fases, pero en otros puede haber menos. 7.2.3 Retardo El retardo consiste en generar una copia retrasada del sonido original con el objeto de mezclarla con éste. La señal retardada se manipula y se mezcla con el sonido original para generar el efecto deseado. El retardo es importante en muchos efectos musicales, entre ellos los filtros, pero también otros más exóticos como el flanger y phaser. 150 C A P Í T U L O 7 • Tratamiento Digital de Señal Hay varios tipos de efectos que se basan en el retardo de la señal: • • • Coros: consiste en mezclar una o más versiones retardadas del sonido con el audio original. El retardo debe ser superior a 5 ms para ser percibido por el oyente y para evitar la interferencia destructiva con la onda original. Las versiones retardadas se pueden manipular para que se diferencien mejor del sonido original. El efecto resultante se asemeja a un coro de voces o instrumentos interpretando una misma pieza musical. Eco: consiste en mezclar una o más versiones retardadas del sonido con el audio original. El retardo debe ser superior a 50 ms para producir este efecto. Se pueden añadir réplicas con distintos retardos para simular múltiples reflexiones del sonido en la distancia. Reverberación: se suele distinguir el eco de la reverberación por el número de réplicas y el retardo entre las mismas. El eco tiene pocas réplicas que son distinguibles en el tiempo por el oyente. En cambio, la reverberación está compuesta de muchas réplicas que llegan en un intervalo reducido de tiempo (<1 ms). La reverberación representa mejor el efecto que se produce al propagarse el sonido en un recinto cerrado. Para simular el efecto de la absorción del sonido por los materiales las réplicas deben estar afectadas por una envolvente de volumen. El tiempo que tarda en extinguirse el eco viene dado por la ecuación de Sabine: T= V c ⋅S⋅a T: tiempo hasta que el eco se atenúa 60 dB (s) c: velocidad del sonido en el aire (340 m/s) V: volumen del recinto (m3) S: superficie del recinto (suelo, techo y paredes) (m2) a: coeficiente de absorción de los materiales Figura 7.3. Nivel sonoro para un recinto con paredes desnudas (rojo) y el mismo recinto con moqueta (negro). • Phaser: consiste en mezclar el sonido original con una versión del mismo con desfase constante, es decir, una copia exacta donde todas las frecuencias han sufrido el mismo desfase. No se trata de un simple retardo temporal, sino de un filtrado de fase lineal. El resultado es un sonido de aspecto sintético, especialmente cuando se usa sobre voces. • Flanger: consiste en mezclar la señal original con una versión que tiene un retardo variable controlado por otra señal. El resultado de mezclar una señal con otra versión retardada es que se produce un filtrado en peine debido a la 151 C A P Í T U L O 7 • Tratamiento Digital de Señal interferencia destructiva en frecuencias equiespaciadas, por lo que el espectro resultante presenta picos y valles que van cambiando de posición según la señal moduladora. Para que se aprecie la variación espectral, el sonido original debe ser rico en armónicos, de manera que siempre quede algo de señal después del filtrado en peine. 7.2.4 Compresión La compresión es un efecto no lineal que reduce el rango dinámico de una señal de audio, es decir, disminuye la diferencia entre la amplitud máxima y la mínima. Este efecto se consigue aplicando una función para la que la relación entre el valor de salida y el de entrada sea menor cuanto mayor sea la amplitud del segundo. Este efecto se utiliza mucho en producción para acomodar sonidos tenues e intensos sobre un registro con rango dinámico limitado sin que se pierdan los primeros ni se saturen los segundos. Este efecto se usaría para convertir una grabación de una con 20 bits a un formato CD de 16 bits. En la siguiente figura podemos observar la relación entre el nivel de entrada de la señal de audio, el nivel de salida, y la reducción de ganancia como consecuencia de la compresión. Figura 7.4. Función de transferencia de un compresor de rango dinámico. 152 C A P Í T U L O 7 • Tratamiento Digital de Señal 7.3 Transformada rápida de Fourier (FFT) Comenzaremos explicando la forma analítica de la transformada de Fourier de una señal discreta en el tiempo (muestreada), también conocida como DTFT (Discrete Time Fourier Transform). Existen numerosas analogías entre la transformada de una señal continua y una discreta, aunque también hay diferencias esenciales que a continuación describiremos. La transformada de Fourier para una señal discreta x[n] se define como: X(ω) = n =∞ ∑ x[n]e − jωn n = −∞ Donde x[n] es una señal discreta infinita en el tiempo y X(ω) es una función continua que se repite periódicamente con un intervalo 2π. Comúnmente se utiliza el intervalo [-π, π] para referirse al rango de frecuencia de la señal discreta. Obsérvese que el rango de frecuencia es adimensional, es decir, no está referido a ninguna unidad de medida de frecuencia ya que la señal muestreada en el tiempo también es adimensional, pues al muestrearla se convierte en una sucesión de valores sin escala temporal. Por tanto, se habla de ω como la frecuencia normalizada en radianes, aunque también se puede sustituir ω por 2πf, en cuyo caso la frecuencia normalizada representa la relación f/fs (fs = frecuencia de muestreo) y su intervalo es de [-0.5, 0.5]. Esta periodicidad es la principal diferencia con la transformada continua de Fourier, que existe de -∞ a ∞. En este punto es donde debemos enlazar con el capítulo 1 de muestreo de señal. La frecuencia π representa la frecuencia de Nyquist de la señal discreta. Recuérdese que el espectro de una señal muestreada se repite periódicamente y que toda componente frecuencial que se encuentre por encima de la frecuencia de Nyquist aparece alienada como una frecuencia dentro del rango permitido (figura 7.5). La explicación de este efecto de “aliasing” la tenemos en la propia definición de la transformada. En Tratamiento Digital de Señal la notación, e-jωn también se puede representar como cos(ωn) + j·sen(ωn), donde j es la unidad imaginaria, también representada como i. Figura 7.5. Muestreo de una señal y alisasing. 153 C A P Í T U L O 7 • Tratamiento Digital de Señal Si x[n] es una señal real, como es habitualmente el caso en el muestreo de audio, la transformada X(ω) es una función donde la parte negativa es la conjugada de la parte positiva, es decir X*(-ω) = X(ω). Si tenemos en cuenta que la transformada se puede representar en términos de amplitud y fase, una señal real produciría una transformada cuyo módulo es simétrico respecto a cero mientras que la fase tiene signo distinto a un lado y otro del cero. Por este motivo, en muchos casos tan sólo se representa la magnitud de la transformada entre 0 y π. En un sistema de procesado de señal las señales en el tiempo no son infinitas y tampoco se puede representar numéricamente una transformada que es una función continua. Para este caso existe una nueva aproximación de la transformada que es la transformada discreta de Fourier o DFT (Discrete Fourier Transform) donde tanto la señal en el tiempo como la señal transformada son discretas y limitadas en el tiempo. La transformada discreta de Fourier de una señal discreta x[n] sobre un número de muestras N se define como: N −1 X[k ] = ∑ x[n]e −j 2 πkn N n =0 La transformada discreta es una aproximación de la transformada continua bajo ciertas condiciones. Si la señal temporal que se analiza es un ciclo exacto de una señal periódica entonces la transformada discreta es una versión muestreada de la transformada continua. Sin embargo, esto no es normalmente así en la señales de audio muestreadas, y por tanto la transformada de una porción de audio dará como resultado una aproximación de su contenido en frecuencia con ciertos artefactos. Estos artefactos son consecuencia del enventanado. Si tomamos una porción de una señal senoidal muestreada y calculamos su transformada, muy probablemente lo que veremos no será una raya a la frecuencia normalizada de la senoidal, sino un lóbulo principal más o menos ancho acompañado de varios lóbulos laterales de amplitud decreciente a ambos lados del primero, como se muestra en la figura 7.6. Lo que ha ocurrido es que transformada se ha convolucionado con transformada de la función rectangular, que es la “máscara” que hemos utilizado para seleccionar un fragmento de la señal senoidal, que por definición es infinita. Tan sólo si el fragmento seleccionado se corresponde exactamente a un ciclo de la señal senoidal la transformada discreta de una señal finita coincidirá completamente con la transformada continua de una señal infinita. 154 C A P Í T U L O 7 • Tratamiento Digital de Señal Figura 7.6. Transformada discreta de una porción de señal sinusoidal. Esta “máscara” utilizada para seleccionar una porción de la señal temporal se denomina ventana, y la forma de la misma determina la resolución con la cual se podrán distinguir las componentes espectrales de la señal temporal. Hay muchos tipos de ventanas según se quiera mejorar la resolución espectral o el rango dinámico del espectro. Las ventanas más comunes para mejorar la resolución espectral (para distinguir mejor las componentes espectrales) son las de von Hann, Hamming, Blackman y coseno alzado. Las ventanas más comunes para mejorar el rango dinámico de la transformada son las de Blackman. A diferencia de la ventana rectangular, todas estas ventanas El uso de una ventana es importante cuando se realiza la transformada de una señal de audio al vuelo, ya que se debe seleccionar una porción de audio suficientemente larga para representar todas las componentes espectrales pero suficientemente corta para generar una carga computacional asequible y un refresco del espectro razonable. El aspecto computacional es importante, ya que la transformada exige un gran número de sumas, multiplicaciones y operaciones trigonométricas que requieren búsquedas en tablas. Para reducir esta carga computacional está la transformada rápida de Fourier or FFT (Fast Fourier Transform) que explota ciertas propiedades de la transformada para reducir el número de operaciones. Así, mientras que la DFT requiere N2 multiplicaciones complejas, algoritmos como el de Cooley-Tukey requieren tan sólo (N/2) log2 N. La restricción de este algoritmo es que sólo puede hacerse sobre un número de muestras que sea potencia de 2, aunque existen otros algoritmos para descomponer muestras de tamaño arbitrario en secciones donde se pueden aplicar los algoritmos rápidos. El algoritmo de mariposa o de Cooley-Tukey consiste en calcular la FFT como combinación de otras FFTs de la mitad de longitud (figura 7.7), prosiguiendo recursivamente hasta llegar a la transformada elemental de 2 muestras (figura 7.8). Esta transformada de dos muestras se calcula de forma muy simple como sumas y restas de las muestras de entrada. Al combinar las FFTs se utilizan unos multiplicadores que corresponden a valores de la exponencial e-jωn evaluada en posiciones fijas. 155 C A P Í T U L O 7 • Tratamiento Digital de Señal Figura 7.7. Descomposición de la transformada en transformadas menores mediante diezmado y operaciones en mariposa. Figura 7.8. FFT básica de 2 puntos, también conocida como operación mariposa. La esencia del algoritmo Cooley-Tukey es separar la FFT en muestras pares e impares, de manera que el sumatorio inicial N −1 X[k ] = ∑ x[n]e −j 2 πkn N n =0 se transforma en X[k ] = N / 2 −1 ∑ x[2n]e −j 2 πk ( 2 n ) N + N / 2 −1 ∑ n =0 x[2n + 1]e −j 2 πk ( 2 n +1) N n =0 Donde el primer sumatorio corresponde a las muestras pares y el segundo a las impares. Si extraemos el término e reescribirse como: X[k ] = N / 2 −1 ∑ x[2n]e −j −j 2 πk N 2 πk ( 2 n ) N del segundo sumatorio, la expresión puede +e −j 2 πk N / 2 −1 N ∑ x[2n + 1]e n =0 −j 2 πk ( 2 n ) N n =0 Obsérvese que la primera parte es sencillamente la DFT de las muestras pares, mientras que la segunda parte es la DFT de las muestras impares multiplicada por un coeficiente. Es decir: −j 2 πk N X[k ] = DFT(pares) + e DFT(impares) Dicho coeficiente se denomina factor de alternancia y habitualmente se representa como WNk . Si la transformada tuviera sólo dos muestras (N=2), dicho término valdría 1 156 C A P Í T U L O 7 • Tratamiento Digital de Señal para k=0 y –1 para k=1. En consecuencia se reduce enormemente el número de operaciones con números complejos a cambio de una mayor complejidad en los cruces de muestras mediante operaciones en mariposa, lo cual no es un problema para un algoritmo recursivo. Un problema adicional cuando se calcula una DFT al vuelo para representar dinámicamente el espectrograma de un sonido es que al tomar bloques consecutivos de muestras existen discontinuidades que pueden causar grandes diferencias en el espectro resultante. Una forma de evitar este problema es tomar secciones de audio solapadas y aplicar un enventanado, de manera que el espectro resultante refleje mejor las variaciones del contenido musical. Este método se conoce como Transformada de Fourier a Corto Plazo (Discrete-Time STFT). Esta transformada se parece a la DFT pero se incluye un término de enventanado w[n]. N −1 X[k , m] = ∑ w[n − m] ⋅ x[n]e −j 2 πkn N n =0 157 C A P Í T U L O 7 • Tratamiento Digital de Señal 7.4 Librerías C++ 7.4.1 Modelos de librerías Comenzaremos esta sección de programación introduciendo los elementos software básicos que implementarán los conceptos previamente explicados. En el desarrollo de aplicaciones en C++ para Tratamiento Digital de señal se pueden utilizar las librerías industriales de las compañías Intel o AMD, o en su caso las librerías de código fuente abierto “Spuc”, que es el acrónimo de Signal Processing Using C++ A DSP Library. Puesto que algunas librerías enunciadas anteriormente son comerciales o excesivamente complejas, en este apartado proponemos una librería para Visual C++ que simplifica en gran medida el desarrollo de aplicaciones de Tratamiento Digital de Señales. Esta librería es de la compañía Mitov, que ofrece una serie de componentes en varios lenguajes dentro de la plataforma Win32 y .NET para el desarrollo de aplicaciones de manejo de señal de alto rendimiento. 7.4.2 Mitov El software de Mitov está especializado en el desarrollo de procesado rápido de señal para audio y video. Las librerías están orientadas a su utilización en plataformas Windows bajo los compiladores de Delphi, Borland C++ Builder, Visual C++ con MFC y .NET. En el presente capítulo se va a explicar cómo utilizar los componentes básicos de la librería dentro del entorno de desarrollo de Microsoft Visual C++.1 El paquete software podemos localizarlo a través de la URL http://www.mitov.com. Una vez descargado, se puede leer en la documentación de la licencia que el producto software es de pago para aplicaciones con salida comercial, mientras que las aplicaciones utilizadas con fines educativos no implican el pago de ninguna cantidad. 7.4.3 Principales librerías de Mitov Los principales componentes incluidos en la “suite” de la compañía Mitov son los que a continuación se enumeran: • • • • 1 VideoLab: es un conjunto de objetos para la captura, reproducción, procesado, manipulaciones geométricas, mezclado, análisis y visualización. Ofrece, entre otras posibilidades, manipulaciones rápidas y complejas de video. AudioLab: permite la captura, reproducción, procesado, mezclado, análisis y visualización de audio. Generalmente utiliza como formato de entrada la extensión WAV. SignalLab: conjunto de componentes para el procesamiento rápido de señal (DSP). Contiene objetos para la visualización de la señal por medio de osciloscopios. VisionLab: es una colección de objetos orientados a la visión básica por computador. El componente permite el desarrollo de aplicaciones de detección Se compilará con el IDE Microsoft Visual C++ 2005 8.0 158 C A P Í T U L O 7 • Tratamiento Digital de Señal • de movimiento para la industria de la seguridad. Incluye algoritmos para la detección de contornos, rostros y objetos. PlotLab: componente para el desarrollo de aplicaciones que requieren el uso de gráficas y representación estadística de datos. Éstas son a grandes rasgos los principales componentes o librerías de la “suite” del software de Mitov. Las librerías concernientes a este capítulo serán AudioLab y SignalLab, para la reproducción de audio y el tratamiento digital de señales respectivamente. 7.4.4 Conceptos básicos de las librerías SingalLab y AudioLab Como se ha comentado anteriormente, la librería SignalLab permite la manipulación de señal por medio de varios objetos fundamentales. Gracias a ellos es posible programar aplicaciones de señal en Visual C++ con apoyo de las librerías de MFC (Microsoft Foundation Classes). Comenzaremos explicando esta primera librería básica. Con el fin de desarrollar una aplicación con SingalLab o AudioLab es imprescindible, en primera instancia, crear un proyecto del tipo MFC, aplicación de diálogo, en un entorno de desarrollo de Microsoft. La peculiaridad de los objetos de visualización de gráficas (Scopes), es que se requiere enlazar el identificador de un componente tipo Label, sobre una aplicación basada en un cuadro de Diálogo, con un objeto del tipo CTSLScope. De esta manera se obtendría el siguiente código que se muestra en el ejemplo: // SignalGeneratorDlg.h : fichero cabecera // #pragma once #include "afxwin.h" #include <CSLScope.h> // Ficheros cabecera de la librería #include <CSLSignalGen.h> // CSignalGeneratorDlg dialog class CSignalGeneratorDlg : public CDialog { // Construcción public: CSignalGeneratorDlg(CWnd* pParent = NULL); // Constructor // estándar // Datos del diálogo enum { IDD = IDD_SIGNALGENERATOR_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV // para intercambio de datos dinámico // Implementación protected: CTSLScope SLScope; // Objeto para visualización CTSLSginalGen SLSignalGen; // Objeto para generación de señal 159 C A P Í T U L O 7 • Tratamiento Digital de Señal protected: HICON m_hIcon; Finalmente, se requiere enlazar el handler de ventana del control Label con el objeto SLScope, dentro del método ::OnInitDialog() de punto de entrada de la aplicación, tal como se muestra en el siguiente fragmento de código fuente: VCL_InitControls( m_hWnd ); SLScope.Open( m_Scope.m_hWnd ); // Aquí enlazamos el handler SLSignalGen.OutputPin.Connect( SLScope.InputPins[ 0 ] ); VCL_Loaded(); return TRUE; // return TRUE unless you set the focus to a control } El resultado se ilustra en la siguiente figura: Figura 7.9 Generación de una forma de onda básica Como se puede observar, con muy pocas líneas de código hemos conseguido una pequeña y básica aplicación que genera una señal elemental sinusoidal y la representa en un osciloscopio virtual. Otra cuestión clave es la interconectividad entre los objetos de señal, es decir, entre el objeto de generación de señal básica y el objeto del osciloscopio. Para conseguir este propósito se recurre a las propiedades de los objetos OutputPin e InputPin, que son las responsables de conectar la salida de generación de onda con la entrada del osciloscopio. De esta manera, y como se explica en el código fuente se lleva a cabo de la siguiente forma: SLSignalGen.OutputPin.Connect( SLScope.InputPins[ 0 ] ); 160 C A P Í T U L O 7 • Tratamiento Digital de Señal Figura 7.10 Diagrama de interconexión entre los objetos Con esto conseguimos conectar la salida del generador de señal con una de las entradas del osciloscopio, puesto que es posible conectar varias entradas para la visualización de señal, con sólo cambiar el índice del array de la propiedad, en el modo que se muestra en el siguiente fragmento de código: SLSignalGen1.OutputPin.Connect( SLScope.InputPins[ 0 ] ); SLSignalGen2.OutputPin.Connect( SLScope.InputPins[ 1 ] ); SLSignalGen3.OutputPin.Connect( SLScope.InputPins[ 2 ] ); . . . Explicaremos ahora el funcionamiento trivial del componente AudioLab. Es importante saber que son imprescindibles dos objetos básicos para la lectura de un fichero de sonido y la salida de audio por el altavoz del sistema. Etos son CTALWavePlayer y CTALAudioOut respectivamente. El primero de ellos permite la lectura de un fichero en formato WAV que posteriormente será redirigirlo a un objeto de SignalLab o al propio objeto de salida de audio. El funcionamiento de estos objetos es muy simple y sigue la misma filosofía que se ha explicado en el ejemplo de la generación de una señal de sonido básica. En este caso, la interconexión para la lectura de un fichero y su reproducción por uno de los canales de salida de audio del sistema, se realizaría de la siguiente manera que se muestra a continuación: { // Construcción public: CAudioPlayerDlg(CWnd* pParent = NULL); // Constructor estándar // Datos del diálogo enum { IDD = IDD_AUDIOPLAYER_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV // para intercambio dinámico de datos // Implementación protected: CTALWavePlayer ALWavePlayer; // Objeto reproductor de sonido CTALAudioOut ALAudioOut; // Objeto de salida de audio protected: HICON m_hIcon; // Funciones miembro virtual BOOL OnInitDialog(); 161 C A P Í T U L O 7 • Tratamiento Digital de Señal afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() }; Como podemos observar, en primer lugar se declaran los objetos CTALWavePlayer y CTALAudioOut. Por último se interconectan en uno de los métodos de la aplicación basada en diálogos; pero en este caso en concreto, se utiliza el método OnInitDialog como punto de entrada a la aplicación. En el código que se muestra a continuación podemos apreciar el funcionamiento de los respectivos objetos de AudioLab: BOOL CAudioPlayerDlg::OnInitDialog() { CDialog::OnInitDialog(); ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } SetIcon(m_hIcon, TRUE); // Establece icono grande SetIcon(m_hIcon, FALSE); //Establece icono pequeño // Lectura del fichero WAV ALWavePlayer.FileName = "C:\\Program Files\\LabPacks\\Visual C++\\Demos\\AVIFiles\\Demo.wav"; // Redirección al canal de salida de audio ALWavePlayer.OutputPin.Connect( ALAudioOut.InputPin ); VCL_Loaded(); // Inicio de los controles Mitov return TRUE; } Obviamente, en la primera línea en negrita indicamos el fichero de sonido WAV2 que necesitamos leer, y por último, interconectamos la salida del objeto reproductor con la entrada del objeto de salida de audio en el sistema. 2 En este tratado únicamente se utilizará el formato WAV por su calidad; aunque es posible utilizar otros formatos en Mitov como WMA. 162 C A P Í T U L O 7 • Tratamiento Digital de Señal En la siguiente figura se ilustra dicha interconexión: Figura 7.11 Diagrama de conexión entre los objetos Para concluir y con el fin de que funcionen correctamente todas las librerías dentro de la aplicación Visual C++ no debemos olvidar llamar a la función VCL_Loaded() que se responsabilizará de iniciar y arrancar todo el sistema de objetos SignalLab y AudioLab. 7.5 Desarrollo de una aplicación de procesado de señal 7.5.1 Diagrama de bloques El propósito de este apartado es explicar cómo se desarrolla una aplicación completa de procesado digital de audio en un entorno conocido como es Visual C++. Con el fin de exponer un ejemplo práctico del mundo real, se ha pretendido implementar un programa que realiza varias operaciones elementales con un objetivo obviamente instructivo de tratamiento de señal. La aplicación consiste en una ventana de diálogo que importa un fichero en formato WAV y realiza cuatro operaciones básicas interconectadas de señal: filtrado paso bajo, filtrado paso banda, filtrado paso alto y ecualización de diez bandas. La salida de esta secuencia será conectada a su vez, a tres osciloscopios. Estos osciloscopios son: representación tiempo frecuencia, onda y espectro FFT. En la imagen que se muestra debajo de esta líneas podemos apreciar la interconexión en el diagrama de bloques: Tiempo frecuencia in out Reproductor ON / OFF in Filtro paso bajo out ON / OFF in Filtro paso banda out ON / OFF in out Filtro paso alto in Onda in Espectro (FFT) in Altavoz del sistema out in Ecualizador ON / OFF Figura 7.12 Diagrama de bloques de la aplicación 163 C A P Í T U L O 7 • Tratamiento Digital de Señal 7.5.2 Declaración de objetos de librería Para comenzar a implementar la aplicación, es requisito imprescindible declarar los objetos que se van a utilizar a lo largo de la aplicación de ejemplo. Con este fin, hemos de utilizar los siguientes objetos que se declararán inicialmente en el fichero de cabecera de la clase del diálogo principal: // tdsDlg.h : Fichero cabecera // #pragma once // Incluye cabeceras de los ficheros de librería #include #include #include #include #include #include #include #include #include #include #include #include <CSLScope.h> <CALWavePlayer.h> <CALAudioOut.h> <CALSpectrum.h> <CSLWaterfall.h> <CALLowPass.h> <CALBandPass.h> <CALHighPass.h> <CALGraphicEqualizer.h> <CSLFourier.h> "afxwin.h" "afxcmn.h" // CtdsDlg dialog class CtdsDlg : public CDialog { // Construcción public: CtdsDlg(CWnd* pParent = NULL); // Constructor estándar // Datos del diálogo enum { IDD = IDD_TDS_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // para intercambio dinámico de datos // DDX/DDV // Implementación protected: CTSLWaterfall SLScope; // Scope Waterfall CTSLScope SLScope2; // Scope en tiempo CTSLScope SLScope3; // Scope para Fourier // Objetos de SignalLab y AudioLab CTSLFourier SLFourier; // Objeto CTALLowPass SLLowPass; // Objeto CTALBandPass SLBandPass; // Objeto CTALHighPass SLHighPass; // Objeto CTALGraphicEqualizer SLEqualizer; CTALWavePlayer ALWavePlayer; CTALAudioOut ALAudioOut; // Objeto CTALSpectrum ALSpectrum; // Objeto 164 que realiza FFT filtro paso bajo filtro paso banda filtro paso alto // Objeto ecualizador // Objeto reproductor salida de audio espectro C A P Í T U L O 7 • Tratamiento Digital de Señal Como podemos observar se declararán los objetos dentro de la clase que hereda de CDialog en la parte protected de la misma. 7.5.3 Inicialización e interconexión de objetos Con la idea de poder comenzar la aplicación con unos valores iniciales por defecto, llamaremos a los constructores y métodos de inicialización de los objetos que se han declarado en la sección anterior. En el siguiente fragmento de código podemos observar la inicialización tanto de los objetos de librería, como de los controles GUI de la aplicación visual. Para conseguir esto sobrecargamos el método OnInitDialog de MFC donde comenzamos a iniciar objetos: // Manejador de mensaje OnInitDialog BOOL CtdsDlg::OnInitDialog() { CDialog::OnInitDialog(); ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE); // Establece icono grande // Establece icono pequeño VCL_InitControls(m_hWnd); // Inicia librería SLScope.Open(m_Scope.m_hWnd); // Conecta scopes con handlers de controles SLScope2.Open(m_Scope2.m_hWnd); SLScope3.Open(m_Scope3.m_hWnd); SLScope3.Visible = false; // Fourier no es visible al principio // Cambia títulos de los scopes SLScope.Title.Text = "Tiempo frecuencia"; SLScope2.Title.Text = "Onda"; SLScope3.Title.Text = "Espectro (FFT)"; // Conecta el Wave player con el filtro paso bajo ALWavePlayer.OutputPin.Connect(SLLowPass.InputPin); // Desactiva filtros 165 C A P Í T U L O 7 • Tratamiento Digital de Señal SLLowPass.Enabled SLBandPass.Enabled SLHighPass.Enabled SLEqualizer.Enabled = = = = false; false; false; false; // Inicia frecuencias de los filtros SLLowPass.Frequency = 5000; SLBandPass.FreqHigh = 11000; SLBandPass.FreqLow = 9000; SLHighPass.Frequency = 5000; SLEqualizer.Channels.Add(10); // Establece coeficientes y frecuencias en el ecualizador SLEqualizer.Channels[0].Coefficient = 1.0; SLEqualizer.Channels[0].Frequency = 31.25; SLEqualizer.Channels[1].Coefficient = 1.0; SLEqualizer.Channels[1].Frequency = 62.5; SLEqualizer.Channels[2].Coefficient = 1.0; SLEqualizer.Channels[2].Frequency = 125.0; SLEqualizer.Channels[3].Coefficient = 1.0; SLEqualizer.Channels[3].Frequency = 250.0; SLEqualizer.Channels[4].Coefficient = 1.0; SLEqualizer.Channels[4].Frequency = 500.0; SLEqualizer.Channels[5].Coefficient = 1.0; SLEqualizer.Channels[5].Frequency = 1000.0; SLEqualizer.Channels[6].Coefficient = 1.0; SLEqualizer.Channels[6].Frequency = 2000.0; SLEqualizer.Channels[7].Coefficient = 1.0; SLEqualizer.Channels[7].Frequency = 4000.0; SLEqualizer.Channels[8].Coefficient = 1.0; SLEqualizer.Channels[8].Frequency = 8000.0; SLEqualizer.Channels[9].Coefficient = 1.0; SLEqualizer.Channels[9].Frequency = 16000.0; SLScope3.Channels.Clear(); SLScope3.Channels.Add(); // Conecta filtro paso bajo con filtro paso banda SLLowPass.OutputPin.Connect(SLBandPass.InputPin); // Conecta filtro paso banda con filtro paso alto SLBandPass.OutputPin.Connect(SLHighPass.InputPin); // Conecta filtro paso alto con ecualizador SLHighPass.OutputPin.Connect(SLEqualizer.InputPin); 166 C A P Í T U L O 7 • Tratamiento Digital de Señal // Conecta ecualizador con scopes SLEqualizer.OutputPin.Connect(SLFourier.InputPin); SLFourier.SpectrumOutputPin.Connect(SLScope3.InputPins[0]); SLEqualizer.OutputPin.Connect(ALAudioOut.InputPin); SLEqualizer.OutputPin.Connect(SLScope2.InputPins[0]); SLEqualizer.OutputPin.Connect(ALSpectrum.InputPin); ALSpectrum.OutputPins[0].Connect(SLScope.InputPin ); // Inicia controles de librería VCL_Loaded(); IniciaSliders(); IniciaVariables(); setIconos(); SLScope.Levels.Axis.Autoscale = false; return TRUE; control } // return TRUE unless you set the focus to a En las primeras líneas se detalla cómo interconectar los controles Label con los objetos CTSLScopes a través de sus handlers de ventana. A continuación se conecta el reproductor de audio con el objeto filtro paso bajo. Para comenzar la aplicación de una manera clara y comprensible se ha preferido desactivar por defecto los objetos de los filtros y el ecualizador, de esta manera se puede observar en el código cómo se establece a false las propiedades Enable de dichos objetos. A continuación se inician las frecuencias de corte de los filtros, así como los coeficientes y frecuencias del ecualizador. Como última acción, se procede a inerconectar los objetos de procesado de señal tal como se ilustró en el digrama de bloques de la figura 7.12 7.5.4 Comportamiento de los filtros y el ecualizador La última parte de la aplicación es implementar el comportamiento de los filtros y el ecualizador. Para implementar los filtros, se ha capturado el mensaje Win32 de desplazamiento de sliders horizontales con el fin de calcular sus propiedades básicas, de esta manera tenemos el siguiente código del método sobrecargado: // Manejador de mensajes de los sliders horizontales afx_msg void CtdsDlg::OnHScroll(UINT SBCode,UINT nPos,CScrollBar *SB) { // Mensaje del slider paso bajo if (SB==(CScrollBar *)&m_SliderBajo) SLLowPass.Frequency = m_SliderBajo.GetPos(); // Mensaje del slider paso banda else if ((SB==(CScrollBar *)&m_SliderFreqBanda) || (SB==(CScrollBar *)&m_SliderAnchoBanda)) { 167 C A P Í T U L O 7 • Tratamiento Digital de Señal double freqlow = (m_SliderFreqBanda.GetPos() m_SliderAnchoBanda.GetPos())/2; if (freqlow < 20.0) freqlow = 20.0; SLBandPass.FreqLow = freqlow; double freqhigh = (m_SliderFreqBanda.GetPos() + m_SliderAnchoBanda.GetPos())/2; if (freqhigh > 20000.0) freqhigh = 20000.0; SLBandPass.FreqHigh = freqhigh; // Mensaje del slider paso alto } else if (SB==(CScrollBar *)&m_SliderFreqAlto) { SLHighPass.Frequency = m_SliderFreqAlto.GetPos(); } } Como se aprecia en el código, tanto para el objeto filtro paso bajo como el filtro paso alto, nos hemos limitado a aplicar la frecuencia de corte a partir de los valores del slider. De esta manera el filtro paso bajo obtiene unos valores comprendidos entre los 30Hz y los 10 KHz, y el el filtro paso alto unos valores comprendidos entre 30 Hz y los 15 Khz para sus sliders. Sin embargo, para el filtro paso banda se le ha aplicado la siguiente ecuación que expresa las frecuencias de corte: Frecuenciaalta = frecuenciacentral + anchobanda 2 Frecuenciabaja = frecuenciacentral − anchobanda 2 Ya sólo resta implementar el comportamiento de ecualizador. Para este propósito se ha capturado el mensaje de movimiento vertical de los sliders, puesto que en sistemas hardware reales típicamente se diseñan en esta posición y por este motivo se han elegido controles GUI que lo imiten. En el siguiente fragmento de código que se expone a continuación se puede observar cómo se obtienen los valores de los coeficientes para el ecualizador: // Manejador de mensajes de los sliders verticales (ecualizador) afx_msg void CtdsDlg::OnVScroll(UINT SBCode,UINT nPos,CScrollBar *SB) { if (SB==(CScrollBar *)&m_Slider1) SLEqualizer.Channels[0].Coefficient m_Slider1.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider2) SLEqualizer.Channels[1].Coefficient m_Slider2.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider3) SLEqualizer.Channels[2].Coefficient m_Slider3.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider4) SLEqualizer.Channels[3].Coefficient m_Slider4.GetPos()) / 100.0; 168 = (200.0 - = (200.0 - = (200.0 - = (200.0 - C A P Í T U L O 7 • Tratamiento Digital de Señal else if (SB==(CScrollBar *)&m_Slider5) SLEqualizer.Channels[4].Coefficient m_Slider5.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider6) SLEqualizer.Channels[5].Coefficient m_Slider6.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider7) SLEqualizer.Channels[6].Coefficient m_Slider7.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider8) SLEqualizer.Channels[7].Coefficient m_Slider8.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider9) SLEqualizer.Channels[8].Coefficient m_Slider9.GetPos()) / 100.0; else if (SB==(CScrollBar *)&m_Slider10) SLEqualizer.Channels[9].Coefficient m_Slider10.GetPos()) / 100.0; } = (200.0 - = (200.0 - = (200.0 - = (200.0 - = (200.0 - = (200.0 - De esta forma se calculan los coeficientes para cada canal del ecualizador con la siguiente ecuación: Coeficientecanal = 200 − decibeliosbanda 100 169 C A P Í T U L O 7 • Tratamiento Digital de Señal 7.5.5 Aspecto final de la aplicación Una vez escrito el código explicado anteriormente y ejecutada la aplicación obtendremos la siguiente interfaz de usuario: Figura 7.13 GUI de la aplicación de ejemplo En la parte izquierda podemos ver el osciloscopio para la respuesta en tiempo frecuencia, como una representación en vista de planta de la distribución de las frecuencias y sus amplitudes, mientras que en la parte derecha se visualiza la representación FFT de la misma señal donde se aprecia claramente en qué rango del espectro se aglutinan las principales frecuencias. Además de la visualización de estos controles gráficos, se permite la posibilidad de aplicar un efecto de filtro paso bajo, cuyo objetivo es atenuar las frecuencias altas, mientras que el filtro paso alto atenúa las frecuencias bajas. El filtro paso banda se encarga de concentrar las frecuencias de la señal en una parte del espectro, por medio del control Frecuencia, mientras que el control Ancho es el responsable de ampliar o reducir el rango de frecuencias filtradas dentro del espectro. Por último, el ecualizador es el responsable de aumentar o disminuir la intensidad en amplitud de las bandas de frecuencia seleccionadas. Si arrancamos la aplicación y activamos el objeto ecualizador podemos apreciar bien cómo se cambia la intensidad de las diferentes bandas al tiempo que movemos verticalmente los sliders. 170 C A P Í T U L O 7 • Tratamiento Digital de Señal Bibliografía y referencias referencias Libros Programación con MFC 6.0 – Herbert Schildt. Editorial Osborne McGraw-Hill, 1999. La Web Mitov documentation http://www.mitov.com MSDN http://msdn2.microsoft.com/es-es/default.aspx Wikipedia: Efectos de audio: http://en.wikipedia.org/wiki/Digital_audio_editor http://en.wikipedia.org/wiki/Sound_effect http://paws.kettering.edu/~drussell/demos.html FFT: http://en.wikipedia.org/wiki/Cooley–Tukey_FFT_algorithm http://en.wikipedia.org/wiki/Butterfly_diagram 171 APÉNDICE A Apéndice A1. Mensajes de la especificación MIDI 1.0 Byte estado D7----D0 Byte(s) datos D7----D0 Descripción Mensajes de canal 1000cccc 0nnnnnnn 0vvvvvvv 1001cccc 0nnnnnnn 0vvvvvvv 1010cccc 0nnnnnnn 0vvvvvvv 1011cccc 0nnnnnnn 0vvvvvvv 1100cccc 0ppppppp 1101nnnn 0ccccccc 1110nnnn 0lllllll 0mmmmmmm Evento note-off. Este evento es enviado cuando finaliza la nota. (nnnnnnn) es el número de la nota. (vvvvvvv) es la velocidad. Evento note-on. Este mensaje se envía cuando comienza una nota (nnnnnnn) es el número de la nota. Presión sobre la tecla (Aftertouch). Este mensaje se envía para informar de la velocidad de presión sobre la tecla. (nnnnnnn) número de la nota. (vvvvvvv) velocidad. Cambio de control. Este mensaje es enviado cuando se cambia un valor de controlador. Véase apéndice A2. (ccccccc) número de controlador. (vvvvvvv) nuevo valor. Cambio de programa Indica un cambio de programa (ppppppp) es el nuevo número de programa. Presión del canal (After-touch). Este mensaje es enviado cuando la presión del canal cambia. Algunos teclados no soportan esta característica. (ccccccc) es el número de canal. Cambio de la rueda de modulación. Este mensaje es enviado para indicar un cambio en la rueda de modulación. La rueda de modulación es medido por el décimo cuarto bit. El valor central es 2000H. (llllll) son los 7 bits menos significativos. (mmmmmm) son los 7 bits más significativos. 173 APÉNDICE 1011nnnn Mensajes de modo de canal 0ccccccc Mensajes de modo de canal 0vvvvvvv Es el mismo código que el Cambio de control(arriba), pero implementa un Modo de control usando unos números de controlador reservados. Los números son: Control Local Cuando el Control Local está off, todos los dispositivos de un canal dado responderán solamente a los datos recibidos sobre MIDI. c = 122, v = 0: Control Local Off c = 122, v = 127: Control Local On Todas las notas desactivadas Cuando llega este mensaje todos los osciladores se desactivan. c = 123, v = 0: Todas las notas Off c = 124, v = 0: Modo Omni Off 11110000 11110001 11110010 11110011 11110100 11110101 11110110 11110111 174 c = 125, v = 0: Modo Omni On c = 126, v = M: Modo mono On (Poly Off) donde M es el número de canales (Omni Off) o 0 (Omni On) c = 127, v = 0: Modo Poly On (Mono Off) (Note: Estos cuatro mensajes también causan todas las notas desactivadas) Mensajes de Sistema 0iiiiiii Sistema exclusivo. 0ddddddd Estos mensajes son específicos del dispositivo e .. independientes del protocolo MIDI. Los bits (iiiiiii) .. es la indentificación de ID del fabricante. 0ddddddd Si el dispositivo reconoce el ID lee los siguientes 11110111 mensajes (ddddddd). En otro caso el mensaje será ignorado. (Nota: Los mensajes de tiempo-real solamente pueden ser entrelazados con los de Sistema Exclusivo) No definido 0lllllll Puntero a la posición de la canción. 0mmmmmmm Es un registro interno de 14 bits que almacena el número de pulsos MIDI. 0sssssss Selección de secuecia. La selección de la canción especifica que secuencia o canción debe ser reproducida.. No definido No definido Petición de tono. Fin de sistema exclusivo APÉNDICE 11111000 11111001 11111010 11111011 11111100 11111101 11111110 11111111 Mensajes de tiempo-real Reloj de tiempo. Oscila 24 veces por cuarto de nota cuando la sincronización es requerida. No definido Comienzo Comienza la reproducción de la secuencia. (Este mensaje vendrá seguido de mensajes de tiempo del reloj). Continua. Continua en el punto donde la secuencia paró. Stop Para la secuencia actual No definido Activa Sensing. El uso de este mensaje es opcional. Cuando es inicialmente enviado, el receptor esperará recibir otro mensaje Active Sensing cada 300ms (máximo), o será asumido que la conexión ha sido terminada. En la terminación, el receptor desactivará todas las voces y volverá al modo normal de operación (Sensing no activo) Reset. Reinicia todos los receptores en el sistema al estado activado. Este mensaje debería ser usado con cuidado y preferiblemente bajo control manual. Particularmente, no debería ser enviado con el sistema encendido. 175 APÉNDICE A2. Lista de mensajes de controladores MIDI 1.0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 176 Bank select Modulation wheel Breath control Indefinido Foot controller Portamento time Data Entry Channel Volume Balance Indefinido Pan Expression Controller Effect control 1 Effect control 2 Indefinido Indefinido General Purpose Controller General Purpose Controller General Purpose Controller General Purpose Controller Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Bank Select Modulation wheel Breath control Indefinido Foot controller Portamento time Data entry Channel Volume Balance Indefinido Pan Expression Controller Effect control 1 Effect control 2 Indefinido Indefinido General Purpose Controller General Purpose Controller General Purpose Controller General Purpose Controller Indefinido Indefinido Indefinido Indefinido Indefinido #1 #2 #3 #4 #1 #2 #3 #4 APÉNDICE 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Damper pedal on/off (Sustain) Portamento on/off Sustenuto on/off Soft pedal on/off Legato Footswitch Hold 2 Sound Controller 1 (Sound Variation) Sound Controller 2 (Timbre) Sound Controller 3 (Release Time) Sound Controller 4 (Attack Time) Sound Controller 5 (Brightness) Sound Controller 6 Sound Controller 7 Sound Controller 8 Sound Controller 9 Sound Controller 10 General Purpose Controller #5 General Purpose Controller #6 General Purpose Controller #7 General Purpose Controller #8 Portamento Control Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Effects 1 Depth Effects 2 Depth Effects 3 Depth Effects 4 Depth Effects 5 Depth Data entry +1 Data entry -1 Non-Registered Parameter Number LSB Non-Registered Parameter Number MSB Registered Parameter Number LSB Registered Parameter Number MSB Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido Indefinido 177 APÉNDICE 116 117 118 119 120 121 122 123 124 125 126 127 178 Indefinido Indefinido Indefinido Indefinido All Sound Off Reset All Controllers Local control on/off All notes off Omni mode off (+ all notes off) Omni mode on (+ all notes off) Poly mode on/off (+ all notes off) Poly mode on (incl mono=off +all notes off) APÉNDICE A3. Lista de instrumentos GM (General MIDI) • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 00 - Piano de cola acústico 01 - Piano acústico brillante 02 - Piano de cola eléctrico 03 - Piano de cantina 04 - Piano Rhodes 05 - Piano con "chorus" 06 - Clavicordio 07 - Clavinet 08 - Celesta 09 - Carillón 10 - Caja de música 11 - Vibráfono 12 - Marimba 13 - Xilófono 14 - Campanas tubulares 15 - Salterio 16 - Órgano Hammond 17 - Órgano percusivo 18 - Órgano de rock 19 - Órgano de iglesia 20 - Armonio 21 - Acordeón 22 - Armónica 23 - Bandoneón 24 - Guitarra española 25 - Guitarra acústica 26 - Guitarra eléctrica (jazz) 27 - Guitarra eléctrica (limpia) 28 - Guitarra eléctrica • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 32 - Bajo acústico 33 - Bajo eléctrico pulsado 34 - Bajo eléctrico punteado 35 - Bajo sin trastes 36 - Bajo golpeado 1 37 - Bajo golpeado 2 38 - Bajo sintetizado 1 39 - Bajo sintetizado 2 40 - Violín 41 - Viola 42 - Violoncello 43 - Contrabajo 44 - Cuerdas con trémolo 45 - Cuerdas con pizzicato 46 - Arpa 47 - Timbales 48 - Conjunto de cuerda 1 49 - Conjunto de cuerda 2 50 - Cuerdas sintetizadas 1 51 - Cuerdas sintetizadas 2 52 - Coro Aahs 53 - Voz Oohs 54 - Voz sintetizada 55 - Éxito de orquesta 56 - Trompeta 57 - Trombón 58 - Tuba 59 - Trompeta con sordina 60 - Corno francés • • • • • • • • • • • • • • • • • • • • • • • • • • • • 64 - Saxo soprano 65 - Saxo alto 66 - Saxo tenor 67 - Saxo barítono 68 - Oboe 69 - Corno inglés 70 - Fagot 71 - Clarinete 72 - Flautín 73 - Flauta 74 - Flauta dulce 75 - Flauta de pan 76 - Cuello de botella 77 - Shakuhachi (flauta japonesa) 78 - Silbato 79 - Ocarina 80 - Melodía 1 (onda cuadrada) 81 - Melodía 2 (diente de sierra) 82 - Melodía 3 (órgano de vapor) 83 - Melodía 4 (siseo órgano) 84 - Melodía 5 (charanga) 85 - Melodía 6 (voz) 86 - Melodía 7 (quintas) 87 - Melodía 8 (bajo y melodías) 88 - Fondo 1 (nueva era) 89 - Fondo 2 (cálido) 90 - Fondo 3 (polisintetiz ador) 91 - Fondo 4 • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 96 - Efecto 1 (lluvia) 97 - Efecto 2 (banda sonora) 98 - Efecto 3 (cristales) 99 - Efecto 4 (atmósfera) 100 Efecto 5 (brillo) 101 Efecto 6 (duendes) 102 Efecto 7 (ecos) 103 Efecto 8 (ciencia ficción) 104 Sitar 105 Banjo 106 Shamisen 107 Koto 108 Kalimba 109 Gaita 110 Violín celta 111 Shanai 112 Campanillas 113 Agogó 114 Cajas metálicas 115 Caja de madera 116 Caja Taiko 117 Timbal melódico 118 Caja sintetizada 119 Platillo invertido 120 Trasteo de guitarra 121 Sonido de respiración 122 Playa 123 Piada de pájaro 124 Timbre de teléfono 179 APÉNDICE (apagada) 29 - Guitarra saturada (overdrive) • 30 - Guitarra distorsionad a • 31 - Armónicos de guitarra • 180 (trompa) 61 - Sección de metal • 62 - Metales sintetizados 1 • 63 - Metales sintetizados 2 • (coro) 92 - Fondo 5 (de arco) • 93 - Fondo 6 (metálico) • 94 - Fondo 7 (celestial) • 95 - Fondo 8 (escobillas) • • • • 125 Helicóptero 126 Aplauso 127 Disparo de fusil APÉNDICE A4. Tabla de frecuencias de notas musicales en Hertzios Do 1: 65,406 Do# 1: 69,296 Re 1: 73,416 Re# 1: 77,782 Mi 1: 82,407 Fa 1: 87,307 Fa# 1: 92,499 Sol 1: 97,999 Sol#1: 103,826 La 1: 110 La# 1: 116,541 Si 1: 123,471 Do 2: 130,813 Do# 2: 138,591 Re 2: 146,832 Re# 2: 155,563 Mi 2: 164,814 Fa 2: 174,614 Fa# 2: 184,997 Sol 2: 195,998 Sol#2: 207,652 La 2: 220 La# 2: 233,082 Si 2: 246,942 Do 3: 261,626 Do# 3: 277,183 Re 3: 293,665 Re# 3: 311,127 Mi 3: 329,628 Fa 3: 349,228 Fa# 3: 369,994 Sol 3: 391,995 Sol#3: 415,305 La 3: 440 La# 3: 466,164 Si 3: 493,883 Do 4: 523,251 Do# 4: 554,365 Re 4: 587,33 Re# 4: 622,254 Mi 4: 659,255 Fa 4: 698,456 Fa# 4: 739,989 Sol 4: 783,991 Sol#4: 830,609 La 4: 880 La# 4: 932,328 Si 4: 987,767 Do 5: 1046,502 Do# 5: 1108,731 Re 5: 1174,659 Re# 5: 1244,508 Mi 5: 1318,51 Fa 5: 1396,913 Fa# 5: 1479,978 Sol 5: 1567,982 Sol#5: 1661,219 La 5: 1760 La# 5: 1864,655 Si 5: 1975,533 Do 6: 2093,005 Do# 6: 2217,461 Re 6: 2349,318 Re# 6: 2489,016 Mi 6: 2637,02 Fa 6: 2793,826 Fa# 6: 2959,955 Sol 6: 3135,963 Sol#6: 3322,438 La 6: 3520 La# 6: 3729,31 Si 6: 3951,066 Do 7: 4186,009 Do# 7: 4434,922 Re 7: 4698,636 Re# 7: 4978,032 Mi 7: 5274,041 Fa 7: 5587,652 Fa# 7: 5919,911 Sol 7: 6271,927 Sol#7: 6644,875 La 7: 7040 La# 7: 7458,62 Si 7: 7902,133 Do 8: 8372,018 Do# 8: 8869,844 Re 8: 9397,273 Re# 8: 9956,063 Mi 8: 10548,082 Fa 8: 11175,303 Fa# 8: 11839,822 Sol 8: 12543,854 Sol#8: 13289,75 La 8: 14080 La# 8: 14917,24 Si 8: 15804,266 181