scieee AI-readable full text Open interactive document viewer

Touch me, a new musical experience : una aplicación multitáctil destinada a la enseñanza musical

García Navarro, Jesús

Full text

Proyecto Final de Carrera Una aplicación multitáctil destinada a la enseñanza musical Autor: Jesús García Navarro Director: Francisco José Abad Cerdá Valencia, 14 de diciembre de 2010 1 Índice 1. Introducción .............................................................................................................................. 3 2. Antecedentes ............................................................................................................................ 5 2.1. Reactable ............................................................................................................................ 5 2.2. Noteput .............................................................................................................................. 6 2.3. Orbitone ............................................................................................................................. 7 2.4. Videojuegos musicales ....................................................................................................... 8 3. Pantallas táctiles ...................................................................................................................... 11 3.1. Resistivas .......................................................................................................................... 11 3.2. Capacitivas ........................................................................................................................ 13 3.3. Imagen óptica ................................................................................................................... 15 4. Proyecto Bathen ...................................................................................................................... 23 4.1. Mesa multitáctil ............................................................................................................... 23 4.2. Librería Bathen ................................................................................................................. 29 4.3. Uso de la librería Bathen .................................................................................................. 34 5. Análisis ..................................................................................................................................... 43 5.1. Descripción General ......................................................................................................... 43 5.2. Requisitos Funcionales ..................................................................................................... 44 6. Diseño ...................................................................................................................................... 51 6.1. Parseador de XML: TinyXML ............................................................................................ 51 6.2. Reproducción de audio: PortMIDI y SDL Mixer ................................................................ 53 6.3. Visión general del diseño ................................................................................................. 58 6.4. Menús ............................................................................................................................... 60 6.5. Composición, pentagrama, instrumentos y notas ........................................................... 61 6.6. Panel de puntuación ........................................................................................................ 70 6.7. Reproducción de notas y melodías .................................................................................. 71 6.8. Carga y guardado de melodías ......................................................................................... 74 6.9. Estados del juego y su control .......................................................................................... 81 7. Resultados ............................................................................................................................... 85 8. Conclusiones............................................................................................................................ 91 2 Anexo A: Manual de usuario ....................................................................................................... 93 Anexo B: Mensajes MIDI ........................................................................................................... 103 Anexo C: Ejemplo de uso de PortMIDI ...................................................................................... 109 Anexo D: Ejemplo de archivo MIDI ........................................................................................... 113 Bibliografía ................................................................................................................................ 117 3 1. Introducción Es innegable la importancia que están adquiriendo las nuevas tecnologías en todos los ámbitos de la sociedad, con la finalidad de facilitar y mejorar la vida diaria. Uno de estos ámbitos es el de la educación, donde los beneficios que aporta su uso son ampliamente reconocidos. Por otra parte, en su constante evolución, la tecnología nos abre las puertas a nuevas formas de interactuar con los ordenadores, las cuales resultan cada vez más cómodas e intuitivas. Una de estas tecnologías son las interfaces multitáctiles, las cuales se están implantando fuertemente en la sociedad durante los últimos años. El proyecto propuesto parte del conocimiento de esta situación y, por ello, tiene como objetivo aplicar las nuevas tecnologías (como es la tecnología multitáctil) al ámbito de la educación. Particularmente, se ha escogido la educación musical porque aporta grandes beneficios en el desarrollo de niños y jóvenes, y su enseñanza se puede afrontar de forma lúdica (1). TouchME es un juego destinado a la enseñanza musical que presenta una forma original de interactuar. Su objetivo es que el usuario aprenda música y, al mismo tiempo, desarrolle sus capacidades motrices (al tratarse de una aplicación multitáctil), sensoriales (al trabajar con estímulos visuales y auditivos) y otras como la memoria y la atención. Además, pretende que todo esto se lleve a cabo de forma divertida y sencilla, de forma que aprender sea un juego. TouchME se basa en una idea básica: Que el usuario componga melodías mediante el uso de notas e instrumentos trabajando directamente sobre la pantalla. El usuario se encarga de ubicar las notas sobre la pantalla y decidir qué instrumentos quiere que las reproduzca. Siguiendo esta línea, TouchME presenta dos modos de juego: Un modo libre en el que usuario no tiene limitaciones a la hora de componer su propia melodía y un modo reto en el que debe reproducir una melodía propuesta por la aplicación. Aparte de la dinámica de juego mencionada, la aplicación suministra otras herramientas, como la posibilidad de guardar melodías compuestas en archivos que, posteriormente, pueden ser reproducidas en otros ordenadores. Otra de esas funcionalidades es la posibilidad de configurar la aplicación a partir de archivos externos. Este proyecto se engloba dentro de un proyecto de mayores dimensiones llamado Bathen, desarrollado por el Instituto de Automática e Informática Industrial (AI2) de la Universidad Politécnica de Valencia. En él se desarrolló una mesa multitáctil, así como un conjunto de librerías y aplicaciones para su uso. Así pues TouchME no parte de cero, si no que hace uso del hardware y software desarrollado en dicho proyecto. Tras el diseño e implementación de la aplicación, se realizaron diversas pruebas en la mesa multitáctil donde se comprobó que TouchME cumplía con los requisitos establecidos en el análisis inicial. Esta memoria describe todo el trabajo realizado, desde los estudios previos hasta la obtención de la aplicación final y de los resultados. En el siguiente capítulo se realiza un 4 análisis de trabajos previos, donde se describen aplicaciones semejantes a TouchME. Para cada una de ellas se realiza un estudio de sus ventajas y desventajas. En el tercer capítulo se ahonda en las diferentes tecnologías utilizadas para la construcción de pantallas táctiles. Se analizan sus ventajas y desventajas y se muestran algunos de los dispositivos que utilizan dicha tecnología. En el cuarto capítulo se describe el proyecto Bathen: Se explica el hardware (la mesa multitáctil) y software (aplicaciones de soporte, librerías, etc.) utilizados. En el capítulo quinto se analizan los requisitos funcionales de la aplicación, es decir, se especifica qué tiene que hacer la aplicación. En el sexto capítulo se presenta el diseño realizado a partir del análisis. Se muestran los diagramas de clases que conforman la estructura de la aplicación y se explican los algoritmos y las estructuras de datos utilizados. Este capítulo responde a la pregunta de cómo se llevará a cabo la aplicación para cumplir con los requisitos especificados. En el capítulo séptimo se muestran los resultados mediante fotografías y capturas obtenidas tras ejecutar la aplicación en la mesa multitáctil. Finalmente, en el último capítulo, se describen las conclusiones que se han alcanzado y se proponen posibles trabajos futuros. 5 2. Antecedentes 2.1. Reactable Reactable es un instrumento electrónico ideado para componer música (2) desarrollado por el Grupo de Tecnología Musical de la Universidad Pompeu Fabra de Barcelona. En esencia, Reactable es una mesa cuya superficie es traslúcida y en cuyo interior se encuentra un proyector. La interfaz es multitáctil y para interactuar con ella se hace uso de diferentes objetos tangibles. Al dejar dichos objetos sobre la superficie e interactuar con ellos es posible combinar sintetizadores, efectos, sonidos y elementos de control para realizar diferentes composiciones. En la Figura 1 se puede ver a un usuario interactuando con Reactable. Figura 1: Persona interactuando con Reactable Fuente: http://www.reactable.com La mesa utiliza la tecnología Rear Diffused Illumination (DI) (3) consistente en iluminar la superficie desde el interior de la mesa mediante luz infrarroja. Esta luz se refleja en la superficie y es captada por una cámara. Esto posibilita detectar tanto la posición de los objetos como la orientación y movimiento de los dedos sobre la superficie. Puesto que la tecnología es multitáctil es posible que varias personas interactúen colaborativamente con la mesa. En cuanto se ubica un objeto sobre la superficie, éste interactúa con los objetos que se encuentran más cercanos. Dichas interacciones se representan gracias al proyector que se encuentra en su interior. De esta forma se aporta feedback visual sobre lo que está ocurriendo. Además, es posible modificar el comportamiento de los diferentes objetos tocando directamente sobre la superficie. En la Figura 2 se puede ver a un usuario dibujando la forma de onda que desea asignar a un oscilador. 12 Figura 7: Pantalla resistiva Fuente: Texas Instruments Figura 8: Divisor de voltaje Fuente: Texas Instruments El controlador determina la posición de la coordenada X aplicando una diferencia de potencial entre las barras conductoras Y+ e Y-. Cuando se produce contacto entre las capas, las barras conductoras de la capa X detectan el voltaje producido y el controlador calcula a partir de dicho valor la coordenada en X. De la misma forma se procede para calcular la coordenada Y: Se aplica una diferencia de potencial entre las barras X+ y X-, se detecta el voltaje producido en la otra capa y, finalmente, se calcula la coordenada en Y. En la Figura 9 se muestra gráficamente cómo se realiza la medición de las coordenadas X e Y. Figura 9: Medición de coordenadas X e Y Fuente: http://www.inotouch.co.kr 13 Ventajas y desventajas Las pantallas resistivas son menos costosas y tienen una mejor resistencia al agua o al polvo. Sin embargo, el uso de múltiples capas hace que las pantallas sean menos brillantes. Otra de las desventajas que se le ha achacado a las pantallas resistivas es que no permiten detectar múltiples contactos, no obstante, algunas compañías como Stantum (7), ya han desarrollado pantallas multitáctiles que incluso son sensibles a la presión. Dispositivos En la Figura 10 se pueden ver distintos dispositivos con pantallas resistivas. Dichas pantallas las podemos encontrar especialmente en TPV’s (Terminales Punto de Venta), PDA’s y en los últimos terminales móviles. 3.2. Capacitivas Las pantallas capacitivas se basan en que el cuerpo humano, al entrar en contacto con una superficie a la que se le aplica un pequeño voltaje, produce una distorsión en su campo electrostático. Estas pantallas están formadas por una membrana de vidrio con una delgada capa metálica colocada sobre la superficie. Al conducir corriente por esta membrana se carga uniformemente. Cuando el usuario toca la pantalla algunas de las cargas se transfieren a él debido a que el cuerpo humano también dispone de capacitancia. Con ello, disminuye la carga de la membrana, es decir, su capacitancia es alterado por el usuario. Este efecto de decrecimiento se mide en los circuitos electrónicos situados en cada esquina de la pantalla. Según la variación de la carga medida en una esquina se puede conocer a qué distancia se encuentra el contacto. Así pues, el punto de toque es calculado por el controlador considerando la variación de carga producida en las cuatro esquinas. En la Figura 11 se puede ver cómo se realiza la medición en las esquinas para calcular las coordenadas X e Y del toque. Figura 10: Pantallas resistivas en TPV, PDA y terminal móvil Fuentes: Oki, HP y Nokia 14 Figura 11: Pantalla capacitiva Fuente: Wikimedia commons Ventajas y desventajas Entre las ventajas de las pantallas capacitivas encontramos que responden mejor que las resistivas, además soportan la tecnología multitáctil. Entre las desventajas encontramos que estas pantallas son más caras que las resistivas y que únicamente pueden ser utilizadas con objetos con capacitancia por lo que no se pueden utilizar los stylus o lápices convencionales. El uso de éste tipo de pantallas es cada vez más popular. Dispositivos Las pantallas capacitivas se pueden encontrar en muchos de los teléfonos de nueva generación. Como se mencionó anteriormente, este tipo de pantallas es cada vez más popular ya que presentan una mejor respuesta y un mayor brillo que las pantallas resistivas. Entre los dispositivos que han salido al mercado y se han popularizado podemos nombrar el Iphone o el Ipod Touch de Apple (Figura 12), que además se caracterizan por ser multitáctiles. Figura 12: Pantallas capacitivas en dispositivos móviles Fuente: Apple y Google 15 3.3. Imagen óptica Las tecnologías ópticas (8), o basadas en el uso de cámaras, requieren de la visión por computador para detectar los toques sobre la superficie de la pantalla. Frustrated Total Internal Reflection (FTIR), Diffused Illumination (DI), Laser Light Panel (LLP), Diffused Surface Illumination (DSI) y Led Light Plane (LED-LP) son algunas de las tecnologías que se basan en el uso de una cámara para construir pantallas multitáctiles. Las pantallas táctiles que utilizan estas tecnologías pueden ser construidas fácilmente por cualquier persona y, además, con un bajo presupuesto. También son destacables por su escalabilidad, ya que es muy sencillo realizar pantallas multitáctiles de gran tamaño. Todas estas tecnologías hacen uso de un sensor óptico (cámara), luz infrarroja y una pantalla. Para comprender el funcionamiento de éstas tecnologías es necesario comprender los fundamentos de la luz infrarroja y los sensores ópticos. Luz Infrarroja: La luz infrarroja forma parte del espectro de luz no visible. Su rango de longitud de onda se encuentra entre la luz visible y las microondas, es decir, entre los 700 y los 1000 nanómetros. La luz infrarroja será la que permita distinguir los toques que se realicen sobre la pantalla de la imagen proyectada sobre la superficie. Cámaras de infrarrojos: Las cámaras comunes suelen bloquear la luz infrarroja dejando pasar únicamente la luz visible. Sin embargo, para construir éstos sistemas basados en la detección de la luz infrarroja, es necesario todo lo contrario. Normalmente es posible quitar el filtro de forma que se detecte la luz infrarroja, no obstante, en las cámaras más caras no es posible ya que el filtro va integrado en la lente y no puede ser modificado. Frustrated Total Internal Reflection (FTIR) FTIR hace referencia a una tecnología desarrollada por Jefferson Han (9). Se basa en la refracción de la luz al cambiar de medio y, particularmente, en la reflexión interna total. La reflexión interna total se produce cuando la luz pasa de un medio a otro con mayor índice de refracción, refractándose de tal modo que no es capaz de atravesar la superficie que separa ambos medios de forma que se refleja totalmente. La reflexión total se produce cuando el haz de luz incide con un ángulo mayor a un cierto valor crítico que se puede calcular mediante la ley de Snell. La tecnología desarrollada por Jefferson Han hace uso de éste efecto. Inyectando luz infrarroja en una superficie de metacrilato, ésta queda atrapada en su interior (Figura 13) Cuando el usuario entra en contacto con la superficie se dice que los rayos de luz infrarroja se ven frustrados, ya que ahora son capaces de pasar a través del metacrilato, iluminando la yema del dedo. La luz reflejada por la yema atraviesa el metacrilato y es captada por la cámara infrarroja. 16 Las capas necesarias para construir la pantalla son las siguientes: • Acrílico: Es necesario que el acrílico tenga un grosor entre 6mm y 10 mm y los bordes pulidos para que la luz infrarroja se inyecte adecuadamente en su interior. • Protector: Es necesario tapar los bordes donde se encuentran los leds infrarrojos para evitar que la luz se escape por los lados. • Difusor: Si no se aplica un difusor, la cámara no sólo captará los toques si no lo que se encuentre tras la pantalla. • Capa de silicona: El mejor o peor funcionamiento de la pantalla dependerá de lo grasos que sean los dedos: Los dedos grasos frustran mejor la luz que los secos. Para solucionar este problema se puede utilizar una capa de silicona sobre el acrílico. Para proyectar imágenes sobre la pantalla y mejorar el funcionamiento se puede utilizar una tela de retro-proyección sobre la silicona. En este caso no resulta necesario utilizar un difusor. Figura 13: Frustrated Total Internal Reflection (FTIR). Fuente: Jefferson Y. Han Diffused Illumination (DI) Esta tecnología se puede utilizar de dos formas: Front Diffused Illumination y Rear Diffused Illumination. Ambas se basan en el contraste que se produce en la imagen al tocar la pantalla. • Front Diffused Illumination: La pantalla se compone de un acrílico y un difusor. La luz ambiental de la habitación ilumina la pantalla, con lo que no son necesarios leds o focos de infrarrojos. Cuando el usuario toca la pantalla interrumpe el paso de la componente infrarroja de la luz, produciendo sombras sobre el difusor. La cámara se encarga de capturar éstas sombras y, a partir de ellas, obtiene las coordenadas del toque. • Rear Diffused Illumination: Con luces infrarrojas se ilumina uniformemente la pantalla por su parte trasera. La pantalla dispone de un difusor en su parte delantera o trasera, de 17 forma que, al tocar sobre la superficie ésta refleja más luz de lo que hace el difusor. Este incremento de luz es detectado por la cámara. En la Figura 14 se puede ver una representación del funcionamiento de esta técnica. La dificultad de éste sistema radica en conseguir una iluminación uniforme sobre la pantalla, ya que si no se consigue habrán partes menos iluminadas en las que resultará necesario presionar más fuerte. Figura 14: Rear Diffused Illumination Fuente: NUI Group Laser Light Panel (LLP) En esta técnica se utiliza la luz infrarroja proveniente de láseres que se ubican sobre la superficie exterior de la pantalla (Figura 15). De esta forma se crea sobre la pantalla una superficie de luz infrarroja. Cuando se produce un contacto sobre la pantalla, la luz colisiona con el dedo y se desprende de forma que se detecta por la cámara. Figura 15: Laser Light Panel Fuente: NUI Group La desventaja de este sistema es el peligro inherente de los láseres ya que pueden resultar dañinos para la vista si se miran directamente. 18 Diffused Surface Illumination (DSI) Esta tecnología utiliza un acrílico especial para distribuir la luz infrarroja. Básicamente sigue el mismo funcionamiento que FTIR pero se diferencia en el acrílico utilizado, el cual se caracteriza por estar compuesto por pequeñas partículas que actúan como pequeños espejos. Cuando se iluminan los bordes del acrílico con leds, la luz infrarroja se distribuye y dispersa por toda su superficie. Cuando el usuario entra en contacto con el acrílico, la luz es dispersada y por tanto detectada por la cámara. En la Figura 16 se representa gráficamente el funcionamiento de esta tecnología. Figura 16: Diffused Surface Illumination Fuente: NUI Group Led Light Plane (LED-LP) Su construcción es similar a FTIR, sin embargo, como se puede ver en la Figura 17, los leds se ubican por encima del acrílico y no en sus bordes. En ese sentido es similar a la tecnología LLP ya que se genera un plano de luz sobre la pantalla. El problema que presenta es que los leds, al producir luz cónicamente no sólo iluminan la superficie si no también los objetos que se encuentran a su alrededor. Para que esto no afecte al funcionamiento se pueden aplicar filtrados software que permitan definir los umbrales de detección. Figura 17: Led Light Plane Fuente: NUI Group 19 Comparativa entre las diferentes técnicas Tecnología Ventajas Desventajas FTIR - No es necesario encerrarla en una caja. - Los toques se visualizan con un alto contraste. - Permite detectar la presión realizada. - No permite reconocer objetos ni marcadores. - Para un buen funcionamiento requiere una capa de silicona. - Requiere un marco de leds que deben ser soldados. Rear DI - No necesita una capa de silicona. - Puede utilizar cualquier material transparente (no solo acrílico). - Funciona con objetos y marcadores. - Los toques aparecen con bajo contraste. - Grandes posibilidades de que se detecten falsos toques. - Es necesaria encerrarla en una caja. - Difícil de conseguir una iluminación uniforme. Front DI - No necesita una capa de silicona. - Puede utilizar cualquier material transparente (no solo acrílico). - No necesita leds. - No es necesario encerrarla en una caja. - Sencillo de construir. - No permite reconocer objetos ni marcadores. - Difícil de conseguir una iluminación uniforme. - Grandes posibilidades de que se detecten falsos toques. - No es tan fiable, depende mucho de la iluminación. LLP - No necesita una capa de silicona. - Puede utilizar cualquier material transparente (no solo acrílico). - No necesita leds. - No es necesario encerrarla en una caja. - Más barata que otras técnicas. - No permite reconocer objetos ni marcadores. - No es sensible a la presión realizada. - Los toques impiden que la luz siga emitiéndose sobre la pantalla (ya que el dedo interrumpe la trayectoria de la luz). Por ello, habrán zonas de la pantalla sin iluminar en las que no será posible detectar toques. DSI - No necesita una capa de silicona. - Funciona con objetos y marcadores. - Es sensible a la presión. - El acrílico es más caro. - Los toques aparecen con bajo contraste. - Posibles restricciones de tamaño debido al grosor del acrílico. LED-LP - No necesita una capa de silicona. - Puede utilizar cualquier material transparente (no solo acrílico). - No es necesario encerrarla en una caja. - Más barata que otras técnicas. - No permite reconocer objetos ni marcadores. - Solo pueden ser utilizados leds de haz estrecho. - Requiere el soldado de leds. Dispositivos Uno de los dispositivos multitáctil Microsoft Surface Illumination (Rear DI) , por lo que en su interior cámaras y focos infrarrojos para iluminar uniformemen especialmente en hoteles, tiendas, restaurantes y lugares de entretenimiento. CUBIT es un sistema multitáctil de código basa su funcionamiento en la técnica creador es redefinir la interacción con el computador y apartar definitivamente el uso del ratón. CUBIT supone una alternati pueden ver los diferentes componentes de una mesa CUBIT. Existen otras empresas de Ideum. MT50 es su principal modelo 50”. Esta mesa está destinada Uno de los dispositivos comerciales que utiliza técnicas de imagen óptica multitáctil Microsoft Surface (Figura 18). Esta mesa se basa en la técnica , por lo que en su interior se puede encontrar un proyector y focos infrarrojos para iluminar uniformemen te la pantalla. Esta mesa se utiliza especialmente en hoteles, tiendas, restaurantes y lugares de entretenimiento. CUBIT es un sistema multitáctil de código abierto que, al igual que Microsoft Surface, basa su funcionamiento en la técnica Rear Diffused Illumination (Rear DI) . La intención de su redefinir la interacción con el computador y apartar definitivamente el uso del CUBIT supone una alternati va más económica que Microsoft Surface. pueden ver los diferentes componentes de una mesa CUBIT. Figura 19: CUBIT Fuente: Nortd Existen otras empresas que también comercializan mesas multitáctiles 50 es su principal modelo (Figura 20) y se caracteriza por tener una pantalla destinada principalmente a ser utilizada en museos y exposiciones. Figura 18: Microsoft Surface Fuente: Microsoft 20 comerciales que utiliza técnicas de imagen óptica es la mesa la técnica Rear Diffused se puede encontrar un proyector , varias Esta mesa se utiliza especialmente en hoteles, tiendas, restaurantes y lugares de entretenimiento. que, al igual que Microsoft Surface, . La intención de su redefinir la interacción con el computador y apartar definitivamente el uso del En la Figura 19 se que también comercializan mesas multitáctiles como es el caso y se caracteriza por tener una pantalla de museos y exposiciones. Pero no todos los dispositivos multitáctiles que utilizan esta tecnología siguen la estructura de una mesa (pantalla horizontal) multitáctiles” , es decir, la pantalla permanece vertical. (Figura 21), un dispositivo presentado recientemente por Microsoft Microsoft Surface pero en formato pizarra Pero no todos los dispositivos multitáctiles que utilizan esta tecnología siguen la (pantalla horizontal) . Otras variantes se basan en , es decir, la pantalla permanece vertical. Un ejemplo es Microsoft TouchWall presentado recientemente por Microsoft que sigue la filosofía de Microsoft Surface pero en formato pizarra . Figura 21: Microsoft TouchWall Fuente: Microsoft Figura 20: MT-50 Fuente: Ideum 21 Pero no todos los dispositivos multitáctiles que utilizan esta tecnología siguen la se basan en crear “paredes Microsoft TouchWall que sigue la filosofía de 28 Para el seguimiento de los toques CCV hace uso del algoritmo de los k-vecinos más próximos. Este algoritmo permite identificar los diferentes blobs y sus movimientos por la pantalla. Para cada blob se busca el blob de la imagen anterior que se encuentre más próximo siempre que la distancia sea inferior a un umbral definido por CCV. Este umbral evita que dos blobs muy alejados puedan considerarse el mismo (se considera que el dedo no puede desplazarse tan rápido como para recorrer una distancia mayor que el umbral entre dos capturas de la webcam). Los blobs más próximos se consideran que son el mismo que se ha desplazado, así pues se les asigna el mismo identificador. Cuando un blob no tiene un vecino próximo se considera que es nuevo. Si existen blobs de la imagen anterior a los que no se les ha asociado ninguno de la imagen actual, se considera que han sido eliminados. Finalmente, la información de los blobs y su trayectoria se codifica y se envía a la aplicación cliente siguiendo el protocolo TUIO. TUIO client application: Esta aplicación debe implementar el protocolo TUIO para poder recibir toda la información relativa a los toques que se realizan. Por suerte, TUIO suministra implementaciones del protocolo en varios lenguajes de programación. Así pues, la aplicación cliente únicamente debe incluir la librería correspondiente. Ésta es la aplicación que verá el usuario en pantalla y con la que interactuará. En el caso del proyecto Bathen se desarrollaron varios juegos destinadas al estudio de su beneficio en el desarrollo neurológico. TUIO protocol: Éste protocolo está codificado siguiendo el formato Open Sound Control (12) que suministra un método eficiente para codificar transmisiones de datos. El protocolo hace uso de paquetes UDP para transmitir los mensajes al cliente. El protocolo define principalmente dos tipos de mensajes: • Mensajes SET: Sirven para informar sobre el estado de un objeto, como su posición u orientación. En el caso de los cursores (toques) la información enviada es la siguiente: set s x y X Y m Donde s es el identificador, x e y la posición, X e Y vectores de velocidad y m es la aceleración. • Mensajes ALIVE: Estos mensajes indican el conjunto de objetos activos que se encuentran sobre la superficie. La información enviada por estos mensajes es la siguiente: alive s_id0 ... s_idN Donde s_id0 ... s_idN es la lista de identificadores de los objetos activos. Para evitar problemas debidos a la pérdida de paquetes, no se utilizan mensajes de tipo ADD o REMOVE cuando se detecta o elimina un objeto. Es el propio receptor el que, al recibir la lista de objetos activos deduce cuáles se han eliminado y cuáles son nuevos. 29 4.2. Librería Bathen El Instituto de Automática e Informática Industrial también ha desarrollado una librería en C++ con la intención de facilitar el desarrollo de aplicaciones para la mesa multitáctil. Esta librería implementa una capa superior sobre las librerías SDL (13) y TUIO (10) con la intención de simplificar su uso (Figura 30). Dependencias: • SDL: Es un conjunto de bibliotecas que proporcionan funciones básicas para realizar operaciones de dibujado 2D, gestión de efectos de sonido y música, y carga y gestión de imágenes. También provee acceso a las funciones de teclado y ratón. Además de SDL se utilizan algunos componentes adicionales como son: o SDL Mixer: Extiende las capacidades de SDL para la gestión y uso de sonido y música en aplicaciones y juegos. Soporta formatos de sonido como Wave, MP3 y OGG. o SDL Image: Extiende notablemente las capacidades para trabajar con diferentes formatos de imagen. Los formatos soportados son los siguientes: BMP, JPEG, TIFF, PNG, PNM, PCX, XPM, LBM, GIF, y TGA. o SDL TTF: Permite usar fuentes TrueType en aplicaciones SDL. • TUIO: Esta librería implementa el protocolo TUIO, suministrando clases y métodos que permanecen a la escucha de nuevos eventos y permiten definir el comportamiento de la aplicación cuando se producen. Descripción general La librería permite al usuario definir manejadores que se encargan de realizar una acción determinada cuando se producen eventos de teclado y toques sobre la pantalla. Así, la aplicación se independiza de los protocolos necesarios para comunicarse con dichos dispositivos. Cuando la librería recibe un evento, automáticamente ejecuta el manejador definido por el usuario. SDL TUIO BATHEN Soporte de aplicación y escenas. Gestor de imágenes y sonidos. Manejadores de toques, teclado y ratón. Paneles, organizadores, grupos, textos, etc. Log. Aplicación Figura 30: Jerarquía de dependencias básica 30 Por otra parte, también se encarga de administrar escenas. Las escenas se caracterizan por gestionar y contener renderables. Los renderables son objetos que se representan en pantalla (imágenes, primitivas, textos…) e implementan dos métodos: update y draw. El método update se encarga de actualizar el estado del objeto, como, por ejemplo, de cambiar la transparencia de una imagen durante su animación. El método draw es el encargado de representar el objeto en pantalla. Las escenas dibujan periódicamente los renderables y se encargan de gestionarlos (añadirlos o borrarlos). Además, permiten definir nuevas update callbacks lo que permite que cualquier clase pueda tener métodos que se ejecuten periódicamente (aunque no tengan que representarse en pantalla y por tanto no sean renderables). La clase App es la que contiene el bucle principal del juego. Es una clase que sigue el patrón singleton por lo que sólo se puede crear una instancia de ella. Esta clase principalmente se encarga de ejecutar los métodos de actualización y dibujado de los renderables de la escena, comprobar si se han producido eventos y ejecutar los manejadores correspondientes, así como de ejecutar los callbacks de actualización que haya podido definir el usuario en una escena. Toda esta funcionalidad se ve reflejada esquemáticamente en la Figura 31. Diagrama de clases UML A continuación se muestra un diagrama de clases simplificado de la librería (Figura 32). Los métodos y atributos menos importantes se han omitido al igual que los setters y getters. A continuación se describen las tareas que realizan cada una de las clases: • Object: Representa a cualquier objeto de la aplicación y aplica un identificador a cada uno de ellos. • Updateable: Representa a aquellos objetos que actualizan su estado con el paso del tiempo. Únicamente tiene un método virtual update que debe ser implementado por sus clases derivadas. • Renderable: Hereda de Updateable. Representa a aquellos objetos que se muestran en pantalla. Tiene un método virtual draw que debe ser implementado por sus clases derivadas. BATHEN Escena Manejadores Ratón Teclado Toques Renderables Update Callback App Figura 31: Arquitectura general de la librería 31 Figura 32: Diagrama de clases de la librería • Image, Primitive, Text: Derivan de renderable e implementan los métodos update y draw. El método update permite modificar el estado del renderable (posición, tamaño, etc.) mientras que el método draw permite dibujarlo. Para el dibujado se hace uso de la librería SDL. • Scene: Contiene la lista de renderables de la escena. Deriva de la clase Updateable y por tanto implementa el método update. Este método se encarga de recorrer la lista de actores (renderables) y ejecutar el método update de cada uno de ellos. Además, la clase tiene como atributo una lista de updateCallbacks que se ejecutan cuando lo hace el método update de la escena. Esto permite al usuario definir clases que derivan de Updateable cuyos métodos update se ejecuten periódicamente. • App: Esta clase contiene el bucle principal de ejecución en su método run(). Dentro del bucle se encarga de ejecutar el método update de la escena. También define listas de manejadores para los toques (CursorHandlers), el ratón (MouseHandlers) y el teclado (KeyboardHandlers). Dentro del bucle principal la clase comprueba si se han producido eventos debidos a toques, al ratón o al teclado. Para comprobar si se han producido eventos de teclado y ratón se hace uso de la librería SDL. Sin embargo, para los eventos de tipo cursor (toques) la clase App tiene como atributo una lista de EventosCursor la cual consulta. La clase Cursor es la encargada de añadir los nuevos eventos que se producen en dicha lista. Si se 32 han producido eventos, la clase App ejecuta los manejadores correspondientes que no son más que callbacks definidas por el usuario. Como se ha mencionado anteriormente, los manejadores son definidos por el usuario mediante el uso de callbacks. En la Figura 33 se puede ver el diagrama UML de las diferentes callbacks utilizadas: Figura 33: Diagrama de clases de las callbacks Como se puede ver en el diagrama todas las callbacks tienen un método llamado fired que deben implementar todas las clases que deriven de ellas. En función del tipo de callback los parámetros del método son diferentes. • UpdateCallback: Recibe como parámetro un entero que indica los milisegundos pasados desde la última vez que se ejecutó. • CursorCallback: El parámetro EventType indica el tipo de evento producido (nuevo cursor o borrado de cursor). El parámetro cursor es el objeto de tipo Cursor que produce dicho evento. • MouseCallback: El parámetro EventType indica el tipo de evento producido (pulsar el botón, soltarlo, etc.). El parámetro x e y indica las coordenadas donde se ha producido el evento. • KeyboardCallback: El parámetro EventType indica el tipo de evento producido (pulsar la tecla, soltarla, etc.). El parámetro key es de tipo entero e identifica la tecla pulsada según el código ASCII. Así pues, para añadir un manejador a la clase App, el programador debe derivar una clase del callback deseado e implementar su método fired. Posteriormente debe instanciar dicha clase y añadirla a la lista de manejadores de la clase App. Diagrama de flujo En la Figura 34 se puede ver un diagrama de flujo en el que se pretende clarificar el flujo de una aplicación que utiliza la librería Bathen. El método run de la clase App es el encargado de la ejecución de la aplicación. Dicho método contiene, básicamente, un bucle que se repite 33 hasta que se indique que la aplicación debe terminar. Los pasos que ejecuta son básicamente tres: despachar los eventos, actualizar la escena y dibujarla. º Renderables Los principales renderables que suministra la librería son: • Image: Permite añadir imágenes, definiendo el tamaño, la posición y el nivel de transparencia. • Text: Permite añadir textos, pudiendo definir su fuente, tamaño y colores. • Primitives: Permite añadir primitivas como círculos, rectángulos o líneas. Siendo posible definir sus dimensiones y colores. • Group: Esta clase sirve para agrupar distintos renderables y manipular el conjunto como un único objeto. • Panel: Esta clase deriva de Group y cumple su misma función de agrupar distintos renderables. La diferencia radica en que esta clase muestra un panel en el que se encierran los renderables. Según las necesidades del programador se pueden crear nuevos renderables, para ello únicamente es necesario derivar nuevas clases. Método Run Mientras no se indique el fin de la aplicación: 1. Despachar eventos 2. Actualizar escena 3. Dibujar escena Método Despachar Eventos Para todos los eventos de ratón: Ejecutar los manejadores del ratón. Para todos los eventos de teclado: Ejecutar los manejadores de teclado. Para todos los eventos de cursores: Ejecutar los manejadores de cursores. Clase App Clase Scene Método Update Para todos los renderables de la escena: ejecutar su método update. Para toda las updateCallbacks definidas: ejecutar su método update. Método Draw Para todos los renderables de la escena: ejecutar su método draw . Figura 34 : Diagrama de flujo de una aplicación 34 Otras funcionalidades • Managers: Estas clases se encargan de cargar los recursos de la aplicación como son las imágenes, los sonidos y las fuentes. Con ello se pretende que no se cargue en memoria un mismo recurso más de una vez. • Organizadores: Reciben como parámetro un grupo y se encargan de organizar los renderables que contiene. Existen dos tipos de organizadores: los verticales y los horizontales y ambos permiten aplicar diferentes alineaciones a los renderables (derecha, centrados, izquierda, etc.). • Log: Permite escribir fácilmente en un log de errores que queda almacenado en un archivo de texto. 4.3. Uso de la librería Bathen En este apartado se muestra un ejemplo de uso de la librería Bathen. En primer lugar se presenta una aplicación mínima y posteriormente se van añadiendo funcionalidades para mostrar el potencial de la librería. Aplicación mínima En la Figura 35 se puede ver el código necesario para crear una aplicación mínima en la que únicamente se muestra una ventana. #include <App.h> #include <Scene.h> using Bathen::Scene; using Bathen::App; int main(int argc, char *argv[]) { //Obtener e inicializar la instancia de la aplicación App *myApp = App::getInstance(); myApp->initApp(argc,argv,Bathen::USE_TUIO); //Creando la escena Scene *scene = new Scene(); //Añadiendo la escena a la aplicación myApp->setScene(scene); //Lanzando la aplicación myApp->run(); return 0; } Figura 35: Código fuente de la aplicación mínima 35 En primer lugar es necesario incluir las cabeceras de la clase App y Scene e indicar su espacio de nombres. El resto de código se define dentro de la función main de la aplicación. En primer lugar se obtiene una instancia de la clase App. Para su inicialización se le pasan los mismos parámetros que a la función main. Así, es posible definir los parámetros de la instancia de la clase App a partir de los parámetros de la aplicación. Otro de los parámetros utilizados es un flag que permite definir opciones como son el uso de cursores (USE_TUIO) o que la aplicación se muestre a pantalla completa (FULLSCREEN). Seguidamente se crea una escena y se añade a la aplicación. Este paso es necesario, pues no se puede crear una aplicación sin hacer uso de las escenas. Finalmente se lanza la aplicación ejecutando el método run de la clase App. El resultado, tras compilar y ejecutar la aplicación, es el mostrado en la Figura 36. Figura 36: Aplicación mínima Hay que tener en cuenta que al iniciar la instancia de la aplicación, ésta lee un archivo de configuración “config.ini” que contiene información relevante como es la resolución de la aplicación, la ruta donde se encuentran los recursos utilizados, etc. Si este archivo no se encuentra la aplicación finalizará. Añadir renderables en la escena A continuación se muestra cómo un añadir un renderable a la escena. En particular, se explica cómo introducir una cadena de texto en la aplicación anterior. Como se ha mencionado anteriormente, la clase Text (derivada de Renderable) permite realizar esta labor, para ello hay que añadir las líneas de código resaltadas en la Figura 37. En primer lugar se añade la cabecera de la case Text y se definen las coordenadas X e Y en las que se desea mostrar el texto. Posteriormente se crea una instancia de la clase Text pasando como parámetros la cadena de texto y la posición de la pantalla donde se quiere mostrar. Finalmente se añade el texto a la lista de renderables de la escena. 36 #include <App.h> #include <Scene.h> #include <Text.h> using Bathen::Scene; using Bathen::App; using Bathen::Text; #define POS_TITLE_X 50 #define POS_TITLE_Y 50 int main(int argc, char *argv[]) { //Obtener e inicializar la instancia de la aplicación … Scene *scene = new Scene(); //Añadiendo texto a la escena Text* title = new Text("Ejemplo",POS_TITLE_X,POS_TITLE_Y); title->setFontSize(48); scene->AddRenderable(title); //Añadiendo la escena a la aplicación … return 0; } Figura 37: Código fuente de la aplicación mínima con un texto de ejemplo El resultado final es el mostrado en la siguiente figura: Figura 38: Aplicación con texto La clase Text permite modificar muchas otras características del texto como son la fuente utilizada, el color del texto o el del fondo. 37 Creación de nuevos renderables En este apartado se explica cómo crear nuevos renderables, para ello se propone la creación de un renderable llamado Star que tenga el aspecto de la Figura 39. Figura 39: Renderable Star Este renderable está compuesto por un círculo blanco y una estrella inscrita. Para el círculo blanco se utilizará la clase Circle definida en las primitivas de la librería Bathen. La estrella se cargará desde una imagen (clase Image) mediante el manager correspondiente. Para facilitar el manejo del círculo y la estrella, ambas se añadirán a un grupo (clase Group). Además a éste renderable se le podrá asignar un cursor (clase Cursor), de forma que se desplace por la pantalla siguiéndolo. En la Figura 40 se puede ver el código de la cabecera del renderable Star. En primer lugar se incluyen todas las cabeceras necesarias (Renderable, Primitives, Image, Group y Cursor) y los espacios de nombres, aunque esta parte no se muestra en la Figura 40 para reducir el código. Seguidamente se define el radio del círculo, de forma que será siempre el mismo. Tras ello comienza la clase Star propiamente dicha. Como se puede ver, ésta hereda de la clase Renderable, por lo que implementa el método update y draw. Destacar que el constructor de la clase recibe el cursor que tendrá asignado el renderable. Además, se define un método itsYourCursor que permite conocer si un determinado cursor está asignado a dicho renderable. #define RADIUS 70 class Star : public Renderable{ public: Star(Cursor* cursor); ~Star(); void update(int ms); void Draw(SDL_Surface *dest, int ox, int oy); bool itsYourCursor(Cursor* c); protected: private: Circle* circle; Image* star; Group* group; Cursor* cursor; }; Figura 40: Cabecera del renderable Star 44 la melodía (número de instrumentos utilizados, notas, etc.). Cuando el usuario completa un nivel pasa a otro de mayor dificultad. La mesa multitáctil en la que se ejecutará TouchME fue diseñada y construida por el Instituto de Automática e Informática Industrial (AI2) de la Universidad Politécnica de Valencia. Además, TouchME utilizará una librería que facilita el desarrollo de aplicaciones para dicha mesa. Tanto las características de la mesa como de la librería utilizada vienen descritas en el capítulo 4 (Proyecto Bathen). Características del usuario Inicialmente la aplicación está destinada a la enseñanza musical por lo que los usuarios en potencia son aquellos que se encuentren en edades de recibir dicha enseñanza. Aún así, la aplicación también puede ser utilizada por personas que quieran o necesiten desarrollar alguna de las capacidades descritas en el punto 5.1 (Perspectiva del producto) independientemente de su edad. Por otra parte, la posibilidad de trabajar con diferentes niveles de dificultad amplía el rango de usuarios que pueden utilizarla. 5.2. Requisitos Funcionales A continuación se enumeran y describen las funcionalidades que debe cumplir la aplicación: 1. Introducción, modificación y eliminación de notas: La aplicación debe permitir añadir notas dentro de una melodía, definiendo el instante de tiempo en el que se encuentra dentro de la melodía, su duración y tono. Para ello se mostrará un esquema semejante al de un pentagrama en el que nueve líneas delimitan ocho zonas. Cada zona representa un tono, siendo la inferior la más grave y la superior la más aguda. El tiempo se representará de izquierda a derecha, por lo que las notas ubicadas a la izquierda se reproducirán antes que las ubicadas a la derecha. En la figura Figura 49 se muestra el pentagrama y su significado. Más agudo Más grave Tiempo Zona 8 Zona 1 … Figura 49 : Esquema del pentagrama 45 Para introducir notas únicamente será necesario tocar sobre alguna de las zonas del pentagrama. El punto de contacto definirá el tono y el instante de reproducción de la nota, la cual se representará mediante un rectángulo (Figura 50). De la misma forma la aplicación permitirá modificar dichos valores e incluso eliminar notas. • Modificar tono: Para modificar el tono el usuario deberá desplazar las notas verticalmente, de una zona a otra. Para ello las tocará y arrastrará a su nueva posición. Dicho movimiento se puede apreciar en la Figura 51. • Modificar instante de reproducción: Para modificar el instante de reproducción el usuario deberá desplazar las notas horizontalmente. Para ello las tocará y arrastrará a su nueva posición (Figura 52). • Modificar duración: Para modificar la duración el usuario deberá tocar con dos dedos la nota y desplazar el dedo ubicado más a la derecha hacia dicho lado para ampliar la duración, o a la izquierda para reducirla Figura 53. Figura 51: Modificación del tono Figura 50 : Representación de una nota Figura 52 : Modificación del instante de reproducción 46 • Eliminar: Para eliminar una nota el usuario deberá desplazarla fuera del pentagrama. Al hacerlo, ésta desaparecerá. En la Figura 54 se muestra el desplazamiento que se debe realizar. Cuando se introduce o modifica una nota, la aplicación deberá reproducir su sonido para que el usuario tenga un “feedback” auditivo y sepa cómo sonará cuando se reproduzca la melodía completa. 2. Selección de instrumento: Una melodía puede contener notas interpretadas por diferentes instrumentos, por ello, la aplicación debe ofrecer la posibilidad de introducir notas eligiendo el instrumento que las reproducirá. Para ello la aplicación mostrará un listado o menú con los instrumentos disponibles en el que uno de ellos siempre estará activo. El usuario podrá cambiar de instrumento activo en cualquier momento. Cuando un instrumento esté activo el usuario sólo podrá añadir y modificar notas de dicho instrumento, permaneciendo el resto en segundo plano. Para diferenciar qué notas son de cada instrumento, éstas tendrán asociadas un color que las identificará con su instrumento. Por ello es necesario que la aplicación establezca claramente el color de cada instrumento. Figura 53: Modificación de la duración Figura 54 : Eliminación 47 3. Reproducción de melodías: En cualquier momento la aplicación debe permitir reproducir la melodía compuesta por el usuario, para ello debe interpretar todas las notas introducidas, teniendo en cuenta sus características e instrumentos que las interpretan. Para ello el usuario dispondrá de un botón que permitirá hacerlo en todo momento. La interfaz mostrará un marcador (una línea vertical) que se desplazará de izquierda a derecha conforme se reproduzca la melodía. De esta forma el usuario sabrá en qué instante se encuentra la reproducción y qué notas se están escuchando. 4. Modos de juego: Como se ha mencionado anteriormente, TouchME plantea dos modos de juego: Un modo creativo en el que el usuario tiene total libertad para componer melodías y un modo ejercicio en el que se plantean pruebas con diferentes niveles de dificultad. • Modo libre: En este modo el usuario dispone de las funcionalidades descritas en los puntos 1, 2 y 3 para componer su propia melodía. En este modo el objetivo es fomentar la creatividad y que el usuario experimente con la música. • Modo reto: En este modo el usuario dispone de las funcionalidades descritas en los puntos 1, 2 y 3 para componer una melodía lo más parecida a otra propuesta por la aplicación (a partir de ahora llamada melodía objetivo). Las melodías objetivo se dividen en diferentes niveles de dificultad de más sencillas a más complejas. Es la aplicación la que compara la melodía del usuario y la melodía objetivo para, en función de la semejanza entre ambas, decidir si el usuario puede pasar el siguiente nivel o no. Así pues, el flujo de una partida es el siguiente: La partida se inicia en el nivel más sencillo (melodía objetivo más simple). En cualquier momento el usuario puede: A. Escuchar la melodía objetivo. B. Componer su propia melodía (añadir, modificar y eliminar notas, asignándoles instrumentos). C. Escuchar su propia melodía. D. Solicitar a la aplicación que compare ambas melodías. a. Si las dos son lo suficientemente parecidas, se pasará al siguiente nivel, con lo que se cargará una nueva melodía objetivo y se borrará la melodía del usuario para volver a iniciar el flujo de la partida. b. Si las melodías no son lo suficientemente parecidas el usuario deberá seguir intentándolo. 48 Es importante que la aplicación disponga de varias melodías para un mismo nivel de dificultad y que escoja aleatoriamente una de ellas. De esta forma se evita que todas las partidas sean iguales. La comparación entre melodías se realizará teniendo en cuenta la semejanza entre tonos, duraciones e instantes de reproducción de las notas, así como los instrumentos que las reproducen. El flujo descrito se puede ver representado gráficamente en la Figura 55. 5. Guardado y carga de melodías: Las melodías creadas en el modo libre deben poder guardarse en ficheros, de forma que posteriormente se puedan reproducir desde cualquier ordenador. De la misma forma, la aplicación podrá cargar dichos ficheros para utilizarlos como niveles en el modo reto o para seguir editándolos en el modo libre. 6. Configuración y extensión de TouchME: La configuración y extensión de TouchME debe ser sencilla. Por ello la aplicación permitirá añadir nuevas melodías al modo reto. Además, será posible editar la lista de Modificar melodía del usuario ¿Superado? Escuchar melodía a reproducir Comparar melodías Melodía a reproducir Melodía del usuario Escuchar melodía del usuario Calcular semejanza Siguiente nivel Cargar nueva melodía Borrar melodía Sí Figura 55 : Flujo de una partida en el modo ejercicio. 49 instrumentos existentes pudiendo añadir nuevos o modificar y eliminar los existentes. Estas configuraciones se almacenarán en archivos xml. 7. Interfaz de usuario: La interfaz de usuario debe ser sencilla y clara de forma que, para alguien que utilice por primera vez la aplicación, resulte intuitiva y fácil de utilizar. Deberá orientarse para aprovechar al máximo las posibilidades que ofrece la tecnología multitáctil. 50 51 6. Diseño 6.1. Parseador de XML: TinyXML Introducción Como se menciona en el análisis de la aplicación, la configuración de TouchME se realizará mediante ficheros XML, por lo que resulta necesaria alguna librería que facilite la lectura de este tipo de archivos. Con ello se pretende evitar implementar un parseador XML desde cero. Para el parseo de ficheros XML se utilizará la librería TinyXML (14). Se ha tomado la decisión de utilizar esta librería debido a que ya ha sido utilizada en otras aplicaciones dentro del proyecto Bathen obteniendo buenos resultados. TinyXML TinyXML es un parseador XML sencillo que se puede integrar con facilidad en otros programas. Permite parsear un archivo XML y obtener una jerarquía de objetos en C++, los cuales pueden ser manipulados y posteriormente volver a ser escritos. La jerarquía de clases de esta librería se puede ver en la Figura 56. Figura 56: Jerarquía de clases de TinyXML Fuente: http://www.grinninglizard.com/tinyxmldocs TiXmlBase es la clase base de la cual heredan TiXmlAttribute y TiXmlNode. • TiXmlAttribute representa atributos, es decir, pares nombre – valor que pueden ser obtenidos mediante los métodos Name y Value respectivamente. • TiXmlNode representa un nodo del fichero XML. De esta clase heredan los diferentes tipos de nodo que se pueden encontrar: Comment, Declaration, Document, Element, Text y Unknown. El método Value retorna un string cuyo significado difiere según su tipo: o Document: Devuelve el nombre del fichero xml. o Element: Devuelve el nombre del elemento. o Comment: Devuelve el texto del comentario. o Unknown: Devuelve el contenido del tag. o Text: Devuelve una cadena de texto. 52 Además, TiXmlNode se encarga de almacenar los nodos hijos y suministrar los métodos para obtenerlos. • TiXmlDocument: Es el nodo de mayor nivel que engloba a todos los demás. Puede ser cargado, guardado y mostrado por pantalla. • TiXmlDeclaration: Los archivos XML correctos sintácticamente comienzan con una línea de declaración del fichero. Aunque no es obligatoria, TinyXML es capaz de leerla. • TiXmlElement: Los elementos pueden contener otros nodos Element, Text, Comment o Unknown. También pueden tener un número arbitrario de atributos, suministrando los métodos necesarios para obtenerlos. • TiXmlComment: Representa un comentario xml. • TiXmlText: Representa una cadena de texto. • TiXmlUnknown: Cualquier tag que no ha sido reconocido como cualquiera de los otros. Para cargar un fichero xml hay que suministrar la ruta del archivo xml a un objeto del tipo TiXmlDocument y lanzar su método LoadFile. Esto hará que automáticamente se genere la jerarquía de objetos. Para recorrer los nodos hijos, todas las clases disponen de los métodos FirstChild, LastChild e IterateChildren ya que se heredan de TiXmlNode. La clase TiXmlElement dispone de métodos para obtener los atributos que pueda tener el nodo (FirstAttribute y LastAttribute). La Figura 57 muestra un archivo XML donde se identifican los diferentes nodos del documento: La jerarquía de objetos devueltos por TinyXML tras leer el XML de la Figura 57 sería la mostrada en la Figura 58. <?xml version="1.0" ?> <MyApp> <!-- Settings for MyApp --> <Messages type=”express”> Welcome </Messages> </MyApp> TiXmlDocument TiXml Declaration TiXml Element TiXml Comment TiXml Element TiXml Text TiXml Attribute Figura 57 : Ejemplo de archivo xml y su correspondencia con objetos de TinyXML 6.2. Reproducción de audio: Análisis de librerías Dada la especificación de capaz de r eproducir notas con diferentes tonos, duraciones e instrumentos completas. Aparte de las notas sonidos, como, por ejemplo, cuando el jugador gane. Algunas de las librerías que requisitos son: SDL Mixer, OpenAL, IrrKlang y PortMIDI SDL Mixer, OpenAL e IrrKlang Estas librerías están orientadas a la cre diferentes formatos de archivos de audio Son librería s con una amplia funcionalidad. reproducirlos en orden, aplicar loops, e incluso, eco, coro, reverberación, etc. En el caso de OpenAl e IrrKlang también es posible reproduc sonido en 3D aprovechando los sistemas de altavoces 5.1 En el proyecto es necesario poder variar el tono (más notas . Si se parte de un sonido almacenado en un nota de piano, es necesario que la librería variaciones de tono y duración. TiXmlDeclaration Figura 58: Jerarquía de objetos Reproducción de audio: PortMIDI y SDL Mixer Análisis de librerías disponibles Dada la especificación de requisitos del capítulo quinto, se deduce que TouchME debe ser eproducir notas con diferentes tonos, duraciones e instrumentos así como de las notas , también resulta interesante que la aplicación una canción de bienvenida al entrar al juego o sonidos de acierto Algunas de las librerías que se han estudiado para dar soporte a estos SDL Mixer, OpenAL, IrrKlang y PortMIDI . SDL Mixer, OpenAL e IrrKlang orientadas a la cre ación de videojuegos y permiten diferentes formatos de archivos de audio . s con una amplia funcionalidad. Permiten organizar los sonidos para reproducirlos en orden, aplicar loops, e incluso, algunas de ellas, permiten aplicar efectos de eco, coro, reverberación, etc. En el caso de OpenAl e IrrKlang también es posible reproduc los sistemas de altavoces 5.1 y 7.1. En el proyecto es necesario poder variar el tono (más agudo o grave) y la duración de l . Si se parte de un sonido almacenado en un archivo , como podría ser el sonido de una nota de piano, es necesario que la librería modifique el sonido para conseguir dichas variaciones de tono y duración. TiXmlDocument TiXmlDeclaration TiXmlElement TiXmlComment TiXmlElement TiXmlAttribute TiXmlText 53 y SDL Mixer TouchME debe ser así como melodías la aplicación reproduzca otros sonidos de acierto dar soporte a estos ación de videojuegos y permiten reproducir Permiten organizar los sonidos para algunas de ellas, permiten aplicar efectos de eco, coro, reverberación, etc. En el caso de OpenAl e IrrKlang también es posible reproduc ir agudo o grave) y la duración de l as , como podría ser el sonido de una para conseguir dichas 60 En los siguientes apartados se va a desgranar el diseño de cada una de las funcionalidades y de las clases implicadas. 6.4. Menús Tras analizar los requisitos funcionales de la aplicación, resulta evidente la necesidad de implementar una clase menú. La selección de instrumento, las diferentes acciones como guardar, reproducir melodías y comprobar la semejanza entre ellas o incluso la selección del modo de juego, será más sencilla con una clase menú que permita disponer en pantalla diferentes ítems. En la Figura 63 se muestra el diagrama UML del menú. Como se puede comprobar éste está compuesto por botones. Figura 63: Diagrama UML de la clase Menu Como se puede ver en el diagrama, los botones tienen un identificador y dos imágenes: una correspondiente al estado activo del botón y otra al estado inactivo. Dependiendo del estado del botón la aplicación mostrará una imagen u otra. El funcionamiento de la clase Menu es sencillo: Tras crear un objeto de esta clase, es posible cargar los botones que lo componen a partir de un fichero XML. Para ello se utiliza el método load que recibe como entrada la ruta y el nombre del fichero. Como se puede ver en la Figura 64, el formato del fichero XML es muy sencillo y contiene la información necesaria para crear los diferentes botones. <Menu> <Button> <Id>1</Id> <SelectedImage>image1.png</SelectedImage> <UnselectedImage>image2.png</UnselectedImage> </Button> … </Menu> Figura 64: Formato de fichero XML donde se define un menú 61 El método newTouch de la clase Menu es el encargado de comprobar si un nuevo cursor intersecta con alguno de los botones. En el caso de que intersecte con alguno de ellos devuelve el identificador del botón y lo selecciona, en caso contrario devuelve -1. 6.5. Composición, pentagrama, instrumentos y notas La estructura principal en la que se almacenan las melodías es el pentagrama, el cual almacena una lista con todas las notas que lo componen además de los diferentes instrumentos disponibles. La composición se encarga principalmente de gestionar dos pentagramas: Uno destinado a almacenar la melodía que compone el usuario y otro para almacenar las diferentes melodías que debe reproducir el usuario en el modo ejercicio. En la Figura 66 se muestra un diagrama UML en el que se pueden ver los atributos, métodos y las diferentes relaciones entre las clases composición, pentagrama, instrumento y notas. Notas Para representar las notas de la melodía se ha implementado una clase Note que gestiona tanto su representación gráfica como su lógica de negocio. Tiene como atributos el instante en el que se debe reproducir, su duración, tono e instrumento. Para su representación gráfica se han utilizado un par de atributos del tipo Rectangle y WireRectangle que heredan de Renderable y permiten representar la nota como un rectángulo. La clase Note también hereda de Renderable e implementa los métodos Draw y Update:  Draw: Se ejecuta periódicamente y se encarga del dibujado por pantalla, en este caso, del rectángulo que representa a la nota.  Update: Se ejecuta periódicamente y se encarga de cambiar el instante de reproducción y la duración de la nota teniendo en cuenta los toques que se producen sobre ella. También se encarga de la animación de la nota cuando ésta se está reproduciendo, cambiando los colores del rectángulo. Creación: Cuando se instancia una nota ésta recibe como parámetros el instante en el que se debe reproducir, su duración, tono y el identificador del instrumento que la reproduce. A partir de esta información la clase calcula los parámetros e inicializa el rectángulo que la representa. Así pues se establece una relación directa entre los atributos de la nota y los atributos del rectángulo que la representa. Esta relación se puede ver en la Figura 65. Atributo de la nota Atributo del rectángulo Instante de reproducción (en ms) Posición X en pantalla Duración (en ms) Ancho del rectángulo Tono (un entero del 0 al 7) Posición Y en pantalla Instrumento (id del instrumento) Color del rectángulo Figura 65: Relación entre los atributos de la nota y los atributos del rectángulo que la representa 62 Figura 66: Diagrama UML de las clases Composition, Staff, StaffDraw, Instrument y Note. 63 La obtención de los atributos del rectángulo se realiza de la siguiente forma:  Posición en X: Se obtiene dividiendo el instante de reproducción (definido en ms) por la variable msPerPixel.  Ancho del rectángulo: Se obtiene dividiendo la duración (definida en ms) por la variable msPerPixel.  Posición en Y: Se obtiene consultando un array que almacena la posición Y correspondiente a cada tono.  Color: Es definido por el instrumento que se encuentre activo en el momento de crear la nota. Modificación: Para modificar los atributos de una nota es necesario actuar sobre ella. Por ello la clase Note tiene definidos dos atributos del tipo Cursor. Cuando se detecta un toque sobre la nota se almacena el cursor en uno de esos atributos mediante el método setCursor. Si la nota tiene asignado un único cursor se interpreta que se desea desplazar la nota. Si la nota tiene asignados los dos cursores (dos dedos sobre la nota) se interpreta que se desea cambiar su duración. Cambio de instante de reproducción y tono de una nota: 1. Se toca una nota con un dedo, con lo que se invoca el método setCursor que asigna el nuevo cursor a la nota. 2. El método update, que se ejecuta periódicamente, modifica la posición de la nota en función de las coordenadas de dicho cursor. De esta forma la nota se desplaza junto con el dedo. 3. Cuando se suelta la nota se elimina el cursor mediante el método deleteCursor. En ese momento puede que la nota no esté dentro de una de las zonas del pentagrama por lo que se ejecuta un método encargado de ajustar la posición Y de la nota dentro de la zona más próxima. 4. Partiendo de la nueva posición X e Y a la que se ha llevado la nota, se recalcula su nuevo instante de reproducción y tono. Cambio de la duración de una nota: 1. Se toca una nota con dos dedos, con lo que se invoca el método setCursor dos veces y se asignan ambos cursores a la nota. Se calcula el punto medio entre ambos (teniendo en cuenta únicamente las coordenadas en X) y las distancias, en valor absoluto, desde el cursor a dicho punto. Los datos calculados se representan gráficamente en la siguiente figura: Nota Cursors dist1 dist2 Figura 67 : Cálculo de distancias entre cursor e s Punto medio 64 Guardar el punto medio permitirá saber qué distancia se ha desplazado cada dedo y en qué sentido. Esto es útil para que el usuario pueda aumentar o reducir la duración por el principio y/o el final de la nota. 2. Cada vez que se ejecuta el método update se comprueba si la distancia entre los cursores y el punto medio ha aumentado o disminuido. En función de las variaciones de distancia de cada cursor respecto al punto central, la nota aumentará o reducirá su duración. Hay que tener en cuenta que, si el cursor de la izquierda se desplaza a la izquierda o a la derecha, la duración de la nota aumentará o se reducirá a costa de modificar el instante en el que se reproduce la nota. En la Figura 68 se muestra un ejemplo en el que el cursor de la derecha se ha desplazado de forma que ha incrementado su distancia al punto medio respecto a la Figura 67. Si el cursor derecho aumenta su distancia al centro, entonces se incrementa la duración de la nota Δdist2 / msPerPixel. Si el cursor derecho disminuye su distancia al centro entonces se reduce la duración de la nota Δdist2 / msPerPixel. Seguidamente se vuelve a recalcular el punto medio y las distancias a los cursores. 3. Cuando se suelta la nota se eliminan ambos cursores mediante el método deleteCursor. Eliminación de una nota: Para eliminar una nota únicamente hay que desplazarla fuera del pentagrama. Como se ha mencionado anteriormente, cuando se suelta una nota tras estar desplazándola, se ejecuta el método deleteCursor. Con ello se ajusta la posición de la nota dentro de la zona o tono más próximo del pentagrama, sin embargo, puede que la nueva posición quede fuera del pentagrama, en ese caso el método deleteCursor devuelve el valor DELETED indicando que la nota debe ser borrada. Reproducción de una nota: Las notas pueden ser reproducidas mediante su método play esto resulta útil para poder escucharlas individualmente. Para reproducir el sonido se utiliza una clase llamada MidiPlayer cuyo funcionamiento se describe posteriormente. Cuando se modifica o crea una nota, se reproduce su sonido para que el usuario tenga un “feedback” de los cambios que está realizando. dist1 dist2 + Δ dist2 Figura 68 : Aumento de la distancia entre un cursor y el punto medio 65 Instrumentos Las notas pueden ser reproducidas por diferentes instrumentos, por lo que es necesario saber en todo momento cuáles son los instrumentos disponibles y sus características. Los atributos de la clase Instrument son su identificador, su “patch number” (entero que representa el sonido que se reproducirá según la especificación MIDI) y los colores de las notas que tengan dicho instrumento asignado. Como se comentará a continuación, es la clase Staff la que carga desde un fichero XML el vector de instrumentos disponibles. Pentagrama La clase pentagrama (Staff) cumple con diversas funcionalidades. La principal es gestionar las notas del pentagrama, para ello dispone de una lista donde almacena todas las notas introducidas. Esta clase también se encarga de gestionar los instrumentos disponibles sabiendo en cada momento cuál es el instrumento activo. Esto permite, por una parte, saber qué instrumento debe asignar a las nuevas notas y, por otra, permite determinar qué notas permanecen activas e inactivas. La clase también implementa un método que se encarga de comparar dos pentagramas. Éste método es el que comprueba la semejanza entre dos listas de notas y determina la puntuación obtenida. Carga de instrumentos La carga de los instrumentos utilizados en el pentagrama se realiza desde un fichero xml. Dicho archivo define para cada instrumento su identificador, el número del instrumento que se reproducirá según la especificación MIDI (Patch Number) y los colores de las notas asignadas a dicho instrumento. El método loadInstruments recibe la ruta del fichero xml y se encarga de añadir los instrumentos a una lista. Por defecto activa el primero de la lista para que siempre haya un instrumento seleccionado. En la Figura 69 se puede ver un ejemplo de fichero en el que se describe la información de los instrumentos. <Instruments> <Instrument> <Id>1</Id> <PatchNumber>1</PatchNumber> <BorderColor>141,179,226</BorderColor> <BackgroundColor>198,217,241</BackgroundColor> </Instrument> <Instrument> <Id>2</Id> <PatchNumber>41</PatchNumber> <BorderColor>185,208,140</BorderColor> <BackgroundColor>214,227,188</BackgroundColor> </Instrument> … </Instruments> Figura 69: Formato de fichero Instruments.xml desde el que se cargan los instrumentos 66 En el Anexo B se puede encontrar una lista de los diferentes Patch Numbers y los instrumentos a los que corresponden. Selección de instrumentos La clase dispone de un método setInstrument que recibe como parámetro el identificador del instrumento que se desea activar. Cuando se llama a esté método se cambia el instrumento seleccionado y se recorre toda la lista de notas para ir activándolas o desactivándolas según el instrumento que tengan asignado. Hay que decir que éste método es llamado cuando se selecciona un instrumento del menú. Cuando el menú de instrumentos detecta que se ha tocado sobre alguno de sus botones, devuelve el identificador del instrumento tocado y se llama al método setInstrument del pentagrama pasándole dicho identificador como parámetro. Por ello es importante que, las definiciones de los instrumentos en Instruments.xml y las del menú en menuInstruments.xml tengan los mismos identificadores. Es decir, si el botón del menú que representa al piano tiene asignado el identificador 1, el instrumento definido en instruments.xml con el “patch number” del piano deberá tener también el identificador 1. Tratamiento de cursores Cuando se produce un nuevo toque sobre el pentagrama se ejecuta su método newTouch. Éste método realiza la siguiente tarea:  Comprueba si el toque se ha realizado sobre alguna de las notas existentes: Para ello recorre la lista de notas, buscando las notas activas y comprobando si se produce intersección entre el toque y el rectángulo que las representa. a. En el caso de que se produzca intersección con alguna de las notas activas, le asigna el cursor. b. En caso contrario se crea una nota nueva asignándole el identificador y los colores del instrumento activo. Cuando se elimina un cursor se lanza el método deleteTouch del pentagrama. Éste método se encarga de recorrer la lista de notas en búsqueda de aquella que tiene asignado el cursor que se desea eliminar. Una vez lo encuentra llama al método deleteCursor de la nota, el cual, como se ha mencionado anteriormente, se encarga de reubicar la nota en alguna de las zonas. Si al hacerlo la nota queda fuera del pentagrama, el método deleteCursor devolverá el valor DELETED , con lo que la clase pentagrama se encargará de borrarla de la lista de notas. Comparación entre pentagramas La clase pentagrama implementa un método compareTo que recibe como parámetro otro pentagrama. Éste método se encarga de comparar ambos pentagramas (ambas listas de notas) y devolver un valor entre 0 y 100 que determina la semejanza entre ambos. La semejanza entre notas se determina según tres parámetros:  La diferencia entre tiempos de reproducción.  La diferencia entre tonos. 67  La diferencia de duración. Para cada uno de los parámetros se define una función que determina la puntuación obtenida al comparar dos notas. Las Figuras 70, 71 y 72 muestran las tres funciones utilizadas para cada uno de los parámetros. Figura 70: Puntuación obtenida según la diferencia entre instantes de reproducción Como se puede ver en la Figura 70, si la diferencia entre los instantes de reproducción de dos notas es menor o igual a 300 ms, se asigna la máxima puntuación (1 punto). A partir de 300 ms la puntuación desciende linealmente hasta llegar a los 0 puntos cuando la diferencia es de 1300 ms. Figura 71: Puntuación obtenida según la diferencia entre tonos Respecto al tono (Figura 71), si no hay diferencia se asigna la mayor puntuación (1 punto), si se diferencian en un tono se asigna la mitad (0,5 puntos) y si la diferencia es mayor o igual a 2 tonos no se asignan puntos. 0 0,2 0,4 0,6 0,8 1 1,2 0 200 400 600 800 1000 1200 1400 Puntuación Diferencia entre instantes de reproducción en milisegundos Instante de reproducción 0 0,2 0,4 0,6 0,8 1 1,2 0 0,5 1 1,5 2 2,5 Puntuación Número de tonos de diferencia Tono 68 Figura 72: Puntuación obtenida según la diferencia entre duraciones Finalmente, en la Figura 72 se puede ver la puntuación obtenida en función de la duración de las notas. Si la diferencia entre ambas es menor o igual a 300 ms, se asigna la máxima puntuación (0,5 puntos). A partir de 300 ms la puntuación desciende linealmente hasta llegar a los 0 puntos cuando la diferencia es de 800 ms. Los valores se han tomado tras probar diferentes alternativas y comprobar que éstos son los que mejor se adecúan a la dificultad deseada (no obstante pueden ser modificados). Como se puede apreciar, la máxima puntuación que se puede obtener es de 2,5 puntos por nota. Eso quiere decir que, si una melodía contiene 10 notas, la máxima puntuación que se podrá obtener cuando se compare con otra será de 25 puntos. Algoritmo de comparación de notas En primer lugar se recorre la lista de notas del pentagrama. Para cada una de ellas se busca aquellas notas con el mismo instrumento asociado que puntuarían tanto por su proximidad en el tiempo como por su semejanza de tono. En otras palabras, se buscan aquellas notas cuya diferencia en el instante de reproducción es menor a 1300 ms y la distancia entre tonos es menor de 2. Esto sirve para descartar la comparación con notas alejadas que únicamente puntuarían por tener una duración semejante. En la Figura 73 se puede ver un ejemplo de pentagrama en el que se representan dos melodías simultáneamente. Cada nota está nombrada con el formato m.n donde m hace referencia al número de melodía y n al identificador de la nota. Así pues la melodía número 1 está compuesta por una única nota (1.A) mientras que la melodía 2 está compuesta por 4 notas. Los diferentes colores indican que se tratan de notas con diferentes instrumentos asociados. Si se comparase la melodía 1 con la melodía 2 (melodía referencia) el algoritmo tomaría la primera y única nota de la primera melodía y buscaría aquellas notas de la melodía 2 que puntuaran tanto por el instante de reproducción como por el tono. Dichas notas son aquellas que se encuentran a menos de 1300 ms y un tono de diferencia de la nota tratada. En la Figura 0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 0 200 400 600 800 1000 Puntuación Diferencia entre duraciones en milisegundos Duración 69 73 se representa un marco discontinuo que encierra aquellas notas que cumplen dicha condición. Puesto que la nota 2.D pertenece a otro instrumento no se tendría en cuenta. Así pues, la nota 1.A se compararía con las notas 2.A y 2.B. Para ello se calcularía la diferencia entre instantes de tiempo, tono y duración; se obtendría la puntuación mediante las funciones expresadas en las gráficas anteriores y, finalmente, se tendría en cuenta la puntuación mayor de las dos calculadas. Este sistema tiene la desventaja de que se pueden obtener puntuaciones mayores de las deseadas. Esto tiene relación con qué melodía es la referencia y cuál es la que se compara con ella. Si una melodía es la referencia, la máxima puntuación que se podrá obtener al ser comparada con cualquier otra, será su número de notas por 2,5. Así pues, en el ejemplo dado, si la melodía 1 es la referencia, la máxima puntuación que se podrá obtener es de 2,5 puntos. Mientras que si la referencia es la melodía 2 la máxima puntuación será 10 puntos. En el ejemplo dado se ha tomado como referencia la melodía 2 con la que se compara la melodía 1, caso en el que no se producen problemas. Pero, ¿Qué pasaría si la referencia fuese la melodía 1? En ese caso se recorrerían las cuatro notas de la melodía 2 y se calcularía para cada una de ellas la puntuación obtenida. Seguramente la nota 2.A puntuaría al compararse con la nota 1.A y la nota 2.B puntuaría también con la nota 1.A, con ello seguramente se obtendría una puntuación total mayor que 2,5. Para evitar este problema el algoritmo penaliza el exceso de notas. Por cada nota que sobrepase el número de notas de la melodía referencia se descuentan 1,5 puntos. Puesto que el valor devuelto por el método compareTo debe estar entre los valores 0 y 100 se calcula el porcentaje de puntuación obtenida. Si la puntuación máxima posible es de 20 puntos y se han obtenido 15, el porcentaje de acierto es 15/20 = 0,75 que, multiplicado por 100, resultaría 75. Dibujado del pentagrama Para representar el pentagrama en pantalla se utiliza la clase StaffDraw, esto es así para separar la lógica de negocio definida por la clase Staff y la interfaz gráfica. StaffDraw es un renderable por lo que implementa los métodos draw y update. Se encarga de mostrar las diferentes líneas y textos del pentagrama. También se encarga de desplazar el marcador de Figura 73 : Ejemplo de comparación entre melodías 2.A 2. B 2. C 2.D 1 .A 1300 ms 1 Tono 76 Ninguno de los campos de la cabecera utiliza el formato variable-length ya que sus tamaños son fijos. • Identificador: El identificador del chunk es siempre el mismo, la cadena “MThd” que en hexadecimal es 0x4D546864. • Tamaño del chunk: El tamaño de la cabecera es siempre de 6 bytes porque los campos formato, número de pistas y división de tiempo son de 2 bytes cada uno. • Formato: Según su valor determina el formato del resto del archivo. Si el formato es 0 significa que el archivo únicamente tiene una pista en la que se encuentra toda la información de la canción. Si el formato es 1 significa que el archivo tiene dos o más pistas. Por convención la primera contiene información de la canción como el título, tempo, la signatura del compás, etc. El resto de pistas contienen los eventos MIDI. Si el formato es 2 el archivo tiene múltiples pistas en las que cada una representa diferentes secuencias que no tienen porqué ser reproducidas simultáneamente. • Número de pistas: Este valor indica el número de pistas definidas en el archivo. • División de tiempo: Contiene la división de tiempo utilizada para decodificar los tiempos delta de los eventos MIDI y convertirlos en tiempo “real”. El significado del tiempo delta se describe en la sección “Eventos MIDI” de este mismo punto. Se puede definir de dos formas: En tics por beat o fotogramas por segundo. Si el bit superior de la palabra es 0, los siguientes 15 bits describen la división de tiempo en tics por beat. En el caso contrario, los 15 bits describen la división del tiempo en fotogramas por segundo. Los tics por beat establecen la relación entre el número de tiempos delta y la duración de una negra. Los valores más comunes se encuentran entre el rango 48 a 960. Los fotogramas por segundo se definen dividiendo los 15 bits en dos valores. Los 7 primeros bits definen el número de fotogramas por segundo, mientras que el resto define cuántos tiempos delta hay en cada fotograma. Así, si la división de tiempo es 0x9978, significa que está definida como fotogramas por segundo. Los primeros 7 bits (0x19) indican que un segundo tiene 25 fotogramas. El resto de bits (0x78) indican que cada fotograma tiene 120 tiempos delta. Si encontramos un evento con un tiempo delta de 3000, en tiempo real, se producirá tras pasar un segundo (1 segundo = 25 fotogramas = 25 fotogramas * 120 tiempos delta = 3000 tiempos delta). Track Chunk Incluye toda la información de la pista, especialmente los eventos MIDI. En la figura se muestra la organización de una pista: Desplazamiento Longitud (bytes) Descripción Valor 0x00 4 Identificador "MTrk" (0x4D54726B) 0x04 4 Tamaño de la pista Explicado a continuación 0x08 Eventos Midi 0x?? 4 Fin de la pista 0x00FF 2F 00 Ilustración 81: Formato de la track chunk • Identificador: El identificador de una pista es siempre el mismo, la cadena “MTrk” que en hexadecimal es 0x4D54726B. 77 • Tamaño de la pista: Indica el número total de bytes usados por todos los eventos de la pista. • Eventos MIDI: Todos los datos de los eventos MIDI siguiendo el formato que se describe a continuación. • Fin de la pista: Indica el fin de la pista. Sigue el formato de los eventos MIDI, pero en realidad únicamente representa metadatos que no se envían al sintetizador. Eventos MIDI El formato de los eventos MIDI es el siguiente: Delta Time Status Data 1 Data 2 Variable - length 1 byte 1 byte 1 byte Como se puede apreciar, el byte status, data1 y data2 no son más que la definición de un mensaje MIDI (ver Anexo B). El punto destacable es el primer valor del tipo variable-length que recibe el nombre de delta time. Delta time define en qué instante se debe reproducir el evento y lo hace respecto al último evento que se llevó a cabo. Éste valor no está en milisegundos ni en ninguna unidad de tiempo: Únicamente es un valor. Para pasarlo a “tiempo real”, MIDI utiliza la división de tiempo especificada en la cabecera del chunk. Así pues, la división de tiempo es la que determina cuántos milisegundos son el valor definido en el delta time. Guardado de melodías Para almacenar una lista de notas en el archivo surge el mismo problema que a la hora de reproducir una melodía. Los mensajes MIDI deben estar ordenados temporalmente y esto supone un problema si las notas están superpuestas en el tiempo (ver el apartado 6.7). Estructura de datos Se decidió que cada instrumento o canal se almacenaría en una pista diferente para que cada una de ellas definiera todas las notas reproducidas por un mismo instrumento. Así pues, antes de escribir en el archivo, el método saveStaff genera, a partir de la lista de notas del pentagrama, una estructura de datos como la siguiente: • Tracks: Es un vector de pistas (Track). • Track: Es un vector de eventos (Event) destinados a un mismo canal. • Event: Almacena información del canal, instante de reproducción, tono de la nota y si debe activarse o desactivarse (ver Figura 75). La variable tracks define un vector con todas las pistas que se vayan a utilizar (tantas como canales sean necesarios o instrumentos se utilicen). Cada una de las pistas define el vector de eventos destinados a dicho canal. 78 Para rellenar esta estructura se sigue el algoritmo descrito en la Figura 82. Crear un vector de tracks “VT”; Para cada instrumento “I” disponible en el pentagrama{ Crear un track “T”; Para cada nota del pentagrama con el instrumento “I” asignado{ Crear un evento “E1” para la activación de la nota; Crear un evento “E2” para la desactivación de la nota; Añadir ambos eventos al track “T”; } Si el track “T” contiene algún evento{ Ordenar los eventos del track “T”; Añadir el track “T” al vector de tracks “VT”; } } Figura 82: Algoritmo para la creación y ordenación de eventos. Escritura del header chunk En este momento ya se puede escribir la cabecera del chunk ya que se conoce el número de pistas que tendrá el archivo. Para eso se utiliza el método writeChunkHeader(byte numChannels)que recibe como parámetro el número de pistas. Éste método escribe por defecto como división del tiempo el valor 0x9E21 , que se corresponde con el formato fotogramas por segundo y establece que cada segundo tiene 30 fotogramas y cada fotograma 33 tiempos delta. Se ha decidido escoger este valor porque permite que los tiempos delta de los eventos se aproximen mucho a milisegundos reales. Por ejemplo, si un tiempo delta es 1000 se corresponderá con el valor real de 1000 milisegundos. Ya que, 1000 ܶ݅݁݉݌݋∆ 33 ܶ݅݁݉݌݋∆ /݂݋ݐ݋݃ݎܽ݉ܽ 30 ݂݋ݐ݋݃ݎܽ݉ܽݏ/ݏ ≅ 1 ݏ Para la escritura en el fichero, genera un buffer de bytes con la información de la cabecera y lo envía al stream de salida mediante el método write. Escritura del track chunk Cuando ya se ha construido la estructura de pistas descrita anteriormente se llama, para cada pista, al método writeTrack que recibe como parámetro el vector de eventos y el byte que identifica la pista o canal (recordemos que se asigna una pista por instrumento). El problema que se plantea ahora es que, antes de escribir todos los eventos, es necesario escribir el tamaño de la pista, es decir, el número de bytes que contiene. Aunque se conozca el número de eventos, no es posible conocer el número de bytes totales que ocupará la pista ya que depende de cuántos bytes se necesiten para representar cada uno de los tiempos delta en el formato value-length. Puede que algunos tiempos delta tengan suficiente con 1 byte mientras que otros puede que necesiten 4 para ser representados. Puesto que para escribir en el archivo se utiliza un buffer de bytes, el método resuelve éste problema creando un buffer lo suficientemente grande para asegurarse que quepan todos los 79 bytes. Aunque aún no se sepa cuántos bytes ocuparán los eventos finalmente, sí se sabe cuál es el número máximo de bytes que podrán ocupar. Este número viene determinado por la siguiente fórmula: ܾܰ = ܰ݁ ∗ ܤ݁݉ܽݔ + ܤ݁ݔݐݎܽ Donde Nb representa el número máximo de bytes que puede ocupar la melodía, Ne el número de eventos (extraíble del vector), Bemax el número máximo de bytes que puede ocupar un evento y Bextra el número de bytes necesarios para realizar el cambio de programa en el canal e indicar el fin de pista. Es decir: Bemax = 4 bytes del tiempo delta + 1byte de status + 2 bytes de datos = 7 bytes Bextra = 3 bytes de cambio de programa + 4 bytes de fin de pista = 7 bytes Una vez inicializado el buffer con tamaño máximo Nb, se añaden los primeros bytes: Los correspondientes al cambio de programa en el canal, para que utilice el instrumento deseado. Seguidamente se recorre el vector de eventos. Para cada uno de ellos se obtiene el instante, se pasa al formato variable-length y se añade al buffer. Después se consulta el tipo de evento (activación o desactivación) y el tono y se generan los bytes de status y datos que también se añaden al buffer. Este proceso se repite para todos los eventos. Por cada byte que se añade al buffer se incrementa un contador. Con ello, al final del proceso se conoce el número de bytes que tiene la pista. Finalmente, se escribe en el fichero el identificador, el tamaño de la pista y los bytes del buffer. Este proceso se repite para cada una de las pistas que existan. En la Figura 83 se describe el algoritmo utilizado en pseudocódigo. byte buffer = byte [Número máximo de bytes posibles] ; añadir mensaje de cambio de canal al buffer; int contador = 3; //Inicializado a 3 porque ya se han añadido 3 bytes Para cada evento “e” del vector{ Byte [4] deltaTime = aValueLength(e.instant); //Devuelve 1, 2, 3 o 4 bytes. Byte statusByte = 0x90 + canal; //Siempre se genera un mensaje del tipo activar nota aunque luego puede que sea utilizado para desactivarla (ver Anexo B). Byte data 1 = calcularData1(e.tone); Byte data2 = calcularVelocidad(e.on); //Dependiendo del tipo de evento el data 2 será 0 (desactivar la nota) o 127 (activarla). Añadir bytes al buffer e incrementar contador tantas unidades como bytes añadidos; } Añadir bytes fin de pista al buffer; contador = contador + 4; //Se suma 4 debido a los bytes añadidos por el fin de pista fichero. escribir(identificador); //Identificador de la pista fichero.escribir(contador); //Tamaño de la pista fichero.escribir(buffer,contador); //Eventos de la pista Figura 83: Algoritmo de escritura de una melodía. 80 Carga de melodías Esta labor la realiza el método loadMidi, el cual lee un archivo .mid y, a partir de él, genera las notas e instrumentos que posteriormente se añaden al pentagrama. Hay que decir que éste método asegura que se carguen correctamente los archivos generados con el método saveMidi, aunque puede que no sea así con archivos generados con otras aplicaciones (debido a las diferentes estructuras que pueden tener los archivos). En primer lugar el método abre el fichero para lectura mediante la variable ifstream. Hay información del fichero que no se tiene en cuenta como el identificador de la cabecera, su tamaño y el número de pistas. Esto es así porque el identificador y el tamaño de la cabecera es un valor invariable y el número de pistas, según está implementado el algoritmo de lectura, no es necesario conocerlo a priori. Lo que sí se comprueba es que la división de tiempo sea la definida por defecto al guardar los archivos, es decir, 0x9E21. Lo que hace que todos los tiempos delta de los eventos sean equiparables a milisegundos en el tiempo real. Este punto se podría mejorar para que el rango de archivos MIDI que se pudiese cargar fuese mayor. Tras ello se inicia un bucle en el que, en primer lugar, se comprueba que existe el identificador de la pista y, posteriormente, se leen y cargan los eventos. Puesto que cada pista es un instrumento, se sabe que todas las notas de una misma pista corresponden a un mismo canal o instrumento y que, además, éste viene identificado en el primer evento de la pista, ya que siempre es un mensaje de cambio de control. Este proceso de lectura de eventos se repite hasta que se lee un fin de pista y posteriormente no se encuentra el identificador de una nueva. Una vez se ha leído el tamaño de la pista, se van leyendo los eventos mediante el método privado getMessage(int &time,byte &code,byte &data1,byte &data2), el cual se encarga de ir leyendo bytes del fichero e interpretarlos. Así pues, cada vez que se llama devuelve el instante del evento y los bytes de estado y datos de los mensajes. Puesto que el valor tiempo delta de los eventos sigue el formato variable-length, el método también se encarga de transformar el valor en un entero normal. Para cada evento que se lee se comprueba si se encarga de activar o desactivar una nota. En el caso de que se trate de un evento de activación se crea una nueva nota asignando el instante, instrumento y tono, y se almacena en una lista de notas. Si se trata de un evento de desactivación se busca en dicho vector la última nota creada con el mismo instrumento y tono, y a partir del instante de reproducción de la nota y el instante de desactivación indicado en evento se asigna la duración a la nota. Este proceso se sigue para todas las pistas y eventos, por lo que al final se construye una lista con todas las notas. Finalmente se ordena dicha lista para que todas las notas queden ordenadas según su instante de reproducción. 6.9. Estados del juego y su control Puesto que TouchME tiene dos modos de juego, se ha decidido que el juego muestre una primera pantalla en la que el usuario escogerá el modo que desea y una segunda que presentará el juego en sí. Por ell • Intro State : En el que se muestra el menú donde el usuario escoge el modo de juego. • Playing State : En el que se muestra el pentagrama, los diferentes menús y donde el usuario puede interactuar según las n Para uniformizar los estados y organizar su código se ha definido un ciclo de vida compuesto por cinco estadios • New : Se crea una instancia del estado. • Load : Carga los recursos necesarios como son las imágenes, ficheros xml, • Start : Lanza el estado y se muestra por pantalla. • Destroy: D estruye todos los recursos cargados y deja de mostrarse por pantalla. No obstante, puede volver a cargarse volviendo al estado Load. • Delete : Se destruye la instancia del estado, para una nueva instancia. Para controlar el cambio entre estado encarga de enviar los eventos encuentre activo en cada momento. El diagrama UML que descr Game y los estados IntroState Figura Cuando se ejecuta el método el estado indicado como argumento y se destruye el resto ( New Load Estados del juego y su control Puesto que TouchME tiene dos modos de juego, se ha decidido que el juego muestre una primera pantalla en la que el usuario escogerá el modo que desea y una segunda que el juego en sí. Por ell o se han diferenciado dos estados de la aplicación: : En el que se muestra el menú donde el usuario escoge el modo de : En el que se muestra el pentagrama, los diferentes menús y donde el usuario puede interactuar según las n ormas del modo escogido. Para uniformizar los estados y organizar su código se ha definido un ciclo de vida compuesto por cinco estadios (Figura 84): Figura 84: Ciclo de vida de los estados : Se crea una instancia del estado. : Carga los recursos necesarios como son las imágenes, ficheros xml, : Lanza el estado y se muestra por pantalla. estruye todos los recursos cargados y deja de mostrarse por pantalla. No obstante, puede volver a cargarse volviendo al estado Load. : Se destruye la instancia del estado, para volver a utilizarlo es necesario crear Para controlar el cambio entre estado s se utiliza la clase Game . Esta clase también se encarga de enviar los eventos cursor (nuevos toques detectados o borrados) al estado que se en cada momento. El diagrama UML que descr ibe la relación entre la clase IntroState y PlayingState se muestra en la Figura 85. Figura 85: Diagrama UML de la clase Game y sus estados Cuando se ejecuta el método changeState de la clase Game se carga ( load el estado indicado como argumento y se destruye el resto ( destroy ). Así es posible pasar Load Start Destroy 81 Puesto que TouchME tiene dos modos de juego, se ha decidido que el juego muestre una primera pantalla en la que el usuario escogerá el modo que desea y una segunda que o se han diferenciado dos estados de la aplicación: : En el que se muestra el menú donde el usuario escoge el modo de : En el que se muestra el pentagrama, los diferentes menús y donde ormas del modo escogido. Para uniformizar los estados y organizar su código se ha definido un ciclo de vida : Carga los recursos necesarios como son las imágenes, ficheros xml, sonidos, etc. estruye todos los recursos cargados y deja de mostrarse por pantalla. No volver a utilizarlo es necesario crear . Esta clase también se (nuevos toques detectados o borrados) al estado que se ibe la relación entre la clase load ) y lanza (start) ). Así es posible pasar de un Delete 82 estado a otro de una forma sencilla. En el diagrama no se muestran todos los atributos y métodos de los estados ya que no son relevantes. IntroState Este estado inicializa el menú donde el usuario elige el modo de juego. Cuando se produce un nuevo toque sobre la pantalla se encarga de llamar al método newTouch del menú, delegando en él el trabajo. Como se ha comentado anteriormente, el menú devuelve un identificador diferente en función del botón que se pulse. Así pues, si no se pulsa sobre ninguno de los botones, el valor retornado será menos uno e IntroState no realizará ninguna tarea. En caso contrario, IntroState actuará según el identificador devuelto. Los tres botones que se muestran son: 1. Modo Libre: Para iniciar el modo libre. 2. Modo Reto: Para iniciar el modo reto. 3. Salir: Termina la aplicación. En el caso de las dos primeras opciones IntroState ejecuta el método changeState de la clase Game para pasar a la pantalla de juego indicando el modo elegido. En el caso de que el usuario pulse Salir, IntroState finaliza la aplicación. PlayingState Este estado es más complejo ya que controla dos menús, una composición y el panel de puntuación. De la misma forma que IntroState, cuando el estado recibe el mensaje de un nuevo cursor, lo reenvía, en este caso, a los menús y a la composición. En primer lugar lo envía al menú de los instrumentos y lee el valor devuelto, si es -1 significa que el usuario no ha tocado ningún botón de dicho menú. Así que el siguiente paso es enviarlo al menú de acciones y, de la misma forma, espera el valor retornado. Si se vuelve a obtener un -1 se envía el mensaje a la composición. La composición recibirá el mensaje y lo pasará al pentagrama del jugador, que, como se ha comentado anteriormente, comprobará si ha caído dentro del pentagrama y en el caso afirmativo realizará la acción correspondiente (añadir una nueva nota o seleccionar la que se ha tocado). Si al enviar el mensaje de nuevo cursor al menú de instrumentos, éste retorna un valor diferente a -1, significa que se ha tocado alguno de sus botones. Así pues PlayingState llama al método setInstrument de la composición pasándole el identificador del instrumento. La composición llama al método setInstrument del pentagrama, el cual, como se ha explicado en apartados anteriores, activa y desactiva las notas correspondientes. En el caso de que el cursor caiga sobre el menú de acciones IntroState actuará de forma diferente según el botón pulsado. El menú de acciones puede tener los siguientes botones: • Reproducir: Reproduce la melodía del usuario. • Guardar: Guarda la melodía del usuario en un archivo MIDI. • Comprobar semejanzas (sólo en modo reto): Compara las dos melodías y muestra el panel de puntuación. 83 • Salir: Vuelve al menú principal. En el caso de que se pulse el botón Reproducir, IntroState llama al método playPlayerStaff de la composición. Si se presiona el de Guardar, se llama al método savePlayerStaff. En el caso de que se pulse comprobar semejanzas, se llama al método getScore de la composición y con el valor obtenido se lanza la animación del panel de puntuación (clase Scoreboard). Finalmente, si se pulsa el botón de salir, se llama al método changeState de la clase Game para que cambie el estado del juego a IntroState. Además, en el modo reto, PlayingState incluye un botón que indica el nivel actual y que, al pulsarlo, reproduce la melodía propuesta. Para ello el estado comprueba el valor retornado, y en el caso de que haya sido pulsado, ordena a la composición que reproduzca la melodía original. 84 85 7. Resultados Tras la implementación y la fase de pruebas en la mesa multitáctil se ha comprobado que la aplicación desarrollada cumple con los requisitos funcionales especificados en el capítulo 5. A continuación se muestran y explican fotografías realizadas a la mesa multitáctil ejecutando la aplicación. Pantalla inicial En esta Figura 86 se puede apreciar la pantalla inicial en la que, aparte del logotipo de la aplicación, se muestra el menú donde se escoge el modo de juego. Como se puede ver la interfaz es clara y sencilla con unos colores vistosos. Figura 86: Pantalla Inicial Pantalla de juego Tras escoger el modo de juego se presenta la pantalla de juego (Figura 87) donde se muestra el pentagrama y los diferentes menús. Esta pantalla varía en función del modo escogido. Figura 87: Modo libre 92 siendo muy utilizado debido a su éxito. También ha permitido aumentar mis conocimientos de programación en C++ y mejorar mis técnicas de programación. Por último, mencionar que se ha profundizado en el análisis de interfaces gráficas de usuario en búsqueda de la opción más sencilla y clara. Como trabajos futuros se plantean varias ampliaciones posibles. Una de ellas es la posibilidad de desplazar todas las notas simultáneamente a la izquierda o la derecha para disponer de más área de trabajo sobre el pentagrama. Esto permitiría introducir más notas y por tanto componer melodías tan largas como el usuario quisiese. Otro punto débil que podría ser ampliado es la mejora de la carga de archivos MIDI. Actualmente la carga está pensada para trabajar con archivos MIDI guardados con la propia aplicación. Esto, aunque no significa que no sea capaz de cargar archivos MIDI generados con otras aplicaciones, puede significar que algunos archivos MIDI no sea capaz de leerlos correctamente. Esta simplificación a la hora de implementar la carga de archivos MIDI se hizo porque su formato puede variar mucho y tratar de abarcar todas las posibilidades se escapaba del objetivo del trabajo. Otra opción sería implementar un método para que, cuando se guarden las melodías en archivos, el usuario pueda introducir el nombre de forma que no se sobrescriba siempre el mismo fichero. De la misma forma se podría implementar el guardado de una partida en modo reto para que un usuario pueda continuarla por donde la dejó la última vez. Finalmente, se podría realizar un estudio de usabilidad a través de la observación y entrevista de varios usuarios. Dicho estudio trataría de averiguar la facilidad de uso y beneficios de la aplicación. Así pues, existe un amplio abanico de posibilidades a la hora de ampliar TouchME. Finalmente, mencionar que los resultados obtenidos han cumplido con los requisitos funcionales especificados al inicio del proyecto como demuestra esta memoria. Además, el desarrollo de éste proyecto ha planteado una serie de problemas cuya resolución me ha permitido ampliar conocimientos y mejorar mi capacidad para afrontarlos. 93 Anexo A: Manual de usuario Introducción TouchME es una aplicación multitáctil destinada a la educación musical. Presenta dos modos de juego en los que el usuario puede componer melodías con diferentes instrumentos. En este manual se explica el funcionamiento y características de la aplicación. Requisitos Para poder ejecutar TouchME en una mesa multitáctil es necesario que el equipo utilizado tenga instalado el sistema operativo Windows XP y la aplicación Community Core Vision para la detección de toques. La tecnología de imagen óptica utilizada por la mesa debe ser FTIR, DI, DSI o LLP, ya que son las tecnologías soportadas por Community Core Vision. Instalación Para instalar la aplicación únicamente es necesario copiar y pegar la carpeta en cualquier directorio del equipo. Para ejecutarla hay que hacer doble clic en el icono TouchME.exe. Pantalla principal Al lanzar la aplicación se mostrará la pantalla principal: Figura 95: Pantalla principal de TouchME En ella se pueden ver tres botones: 1. Modo libre: En este modo el usuario puede componer su propia melodía. El objetivo es fomentar la creatividad y que el usuario experimente con la música. 2. Modo reto: En este modo el usuario debe componer una melodía lo más parecida a otra propuesta por la aplicación. Los ejercicios se organizan en diferentes niveles de dificultad, comenzando con melodías sencillas y acabando con las más complejas. 3. Salir: Finaliza la aplicación. 94 Para seleccionar uno de los botones únicamente hay que tocarlo sobre la pantalla. Pantalla de juego Los dos modos de juego se caracterizan por tener la misma estructura: Un pentagrama donde se introducen las notas, un menú de selección de instrumentos y un menú de acciones. Las diferentes partes de la pantalla de juego se pueden ver en la Figura 96. Figura 96: Pantalla de juego 1. Pentagrama: Se compone de nueve líneas que dividen la pantalla en ocho zonas correspondientes a las notas de la escala musical. Las zonas inferiores corresponden a las notas más graves y las superiores a las más agudas. En cada zona se indica mediante una etiqueta el nombre de la nota correspondiente. El eje horizontal del pentagrama representa el tiempo, por lo que las notas situadas a la izquierda se reproducen antes que las situadas a la derecha. 2. Notas activas: Dentro del pentagrama se representan las notas como rectángulos cuyo color depende del instrumento que las reproduce. Cuando dicho instrumento está seleccionado, sus notas pasan a estar activas y se muestran en primer plano. Al estar activas el usuario puede modificarlas o eliminarlas. 3. Notas inactivas: Éstas notas se muestran semitransparentes y en segundo plano. Se tratan de notas que no están activas porque no está seleccionado el instrumento que las reproduce. Al estar inactivas el usuario no puede modificarlas ni eliminarlas. 4. Marcador de reproducción: Cuando se reproduce la melodía esta línea se desplaza de izquierda a derecha indicando el instante de reproducción. Éste marcador permite saber al usuario por dónde va la reproducción y saber qué está escuchando. 5. Menú de instrumentos: En este menú se selecciona el instrumento que se desea activar. Cuando un instrumento se selecciona, las notas correspondientes pasan a estar activas y el resto inactivas. Cualquier nota nueva que se introduzca tendrá asignado el instrumento seleccionado. En la Figura 96 se puede ver que el instrumento activo es el piano, por ello las dos notas azules están activas y las otras tres verdes (correspondientes al violín) están inactivas. En esta situación, el usuario únicamente puede modificar y eliminar las dos notas activas y, además, si introduce nuevas notas 95 éstas serán reproducidas por el piano. Por tanto, para modificar o añadir notas de, por ejemplo, el violín, deberá seleccionar dicho instrumento en el menú. 6. Menú de acciones: Éste menú varía en función del modo de juego. Permite realizar diferentes acciones como son reproducir la melodía, guardarla o volver a la pantalla principal. Añadir, modificar y eliminar notas • Añadir notas: Como se puede ver en la Figura 97, para añadir nuevas notas debe tocar sobre la zona del pentagrama donde quiera introducirla. Automáticamente se añadirá la nota asignándole el instrumento activo. • Modificar notas: o Cambiar de tono: Para modificar el tono de una nota (Figura 98) se debe tocar sobre ella y arrastrarla verticalmente (manteniendo el dedo sobre la superficie). o Cambiar instante de reproducción: Para modificar el instante en el que se reproduce una nota (Figura 99) se debe tocar sobre ella y arrastrarla horizontalmente (manteniendo el dedo sobre la superficie). La melodía se reproduce de izquierda a derecha por lo que las notas situadas a la izquierda se reproducirán antes que las ubicadas a la derecha. Figura 97 : Introducción de notas Figura 98 : Modificación del tono de una nota 96 o Cambiar duración: Para modificar la duración de una nota (Figura 100) se debe tocar sobre ella con dos dedos y, manteniéndolos sobre la pantalla, desplazar el dedo derecho hacia la derecha (para aumentar la duración) o hacia la izquierda (para reducir la duración). • Eliminar notas: Para eliminar notas del pentagrama (Figura 101) hay que tocar sobre ella y desplazarla, manteniendo el dedo sobre la pantalla, fuera del pentagrama. Al soltarla desaparecerá. Figura 99: Modificación del instante de reproducción de una nota Figura 100 : Aumento de la duración de una nota Figura 101: Eliminación de una nota 97 Menú de instrumentos En la parte inferior izquierda de la pantalla se muestra un menú con los diferentes instrumentos disponibles (Figura 102). El instrumento activo se muestra con mayor contraste que el resto. Para poder cambiar la selección hay que tocar el instrumento deseado y, automáticamente, se activarán las notas correspondientes. Figura 102: Menú de instrumentos En la parte inferior de cada instrumento se muestra su nombre dentro de un rectángulo cuyo color identifica las notas que reproduce. Modo libre En este modo el usuario dispone del pentagrama y el menú de instrumentos para componer su propia melodía conforme se ha descrito anteriormente. Todo ello se puede ver en la Figura 103. Figura 103: Interfaz de usuario en el modo libre Además, en la esquina inferior derecha de la pantalla dispone de un menú de acciones compuesto por tres botones: 1. Guardar: Se encarga de guardar la melodía en un archivo .mid en el mismo directorio donde se encuentra el ejecutable de la aplicación TouchME.exe. 2. Reproducir: Se encarga de reproducir la melodía compuesta. 3. Salir: Sale del modo libre y vuelve a la pantalla principal. 98 Modo Reto En este modo el usuario debe imitar una melodía lo más parecida a la que reproduce el ordenador. La interfaz (Figura 104) presenta una pequeña variación con respecto al modo libre. Figura 104: Interfaz de usuario en el modo reto El cambio más novedoso lo encontramos en la parte superior de la pantalla: 1. Reproducción de la melodía original: En zona superior encontramos un indicador que muestra el nivel de dificultad en el que nos encontramos. Si tocamos sobre ese botón se reproducirá la melodía que debemos componer. Dentro del menú acciones encontramos los siguientes botones: 2. Reproducir: Se encarga de reproducir la melodía compuesta. 3. Comprobar semejanza: Muestra un panel de puntuación como el de la Figura 105, que se encarga de indicar la semejanza entre ambas melodías. Figura 105: Puntuación del nivel Si el porcentaje de semejanza es superior al 75% se pasa al siguiente nivel, en caso contrario el usuario debe seguir intentándolo. 4. Salir: Sale del modo libre y vuelve a la pantalla principal. 99 Edición de niveles Mediante el fichero levels.xml que se encuentra en el directorio \media\TouchME\levels, se pueden configurar los niveles del modo reto. Esto permite añadir las melodías guardadas en el modo libre en alguno de los niveles del juego. En el archivo xml cada melodía viene definida por la línea: <Level n="num"> fichero </Level> Donde num es el número del nivel y fichero es la ruta donde se encuentra el archivo MIDI que deseamos añadir. En la siguiente figura se muestra un ejemplo en el que se definen tres niveles de dificultad. < Levels> <Level n="1"> 001.mid </Level> <Level n="1"> 002.mid </Level> <Level n="2"> 003.mid </Level> <Level n="3"> 004.mid </Level> </Levels> Figura 106: Formato de fichero levels.xml Edición de instrumentos Para editar los instrumentos disponibles en la aplicación es necesario modificar dos ficheros xml: Instruments.xml en /media/TouchME y menuInstrument.xml en el directorio /media/TouchME/menu. Instruments.xml El primer fichero se encarga de definir los identificadores de los instrumentos, su pach number (número de instrumento en el estándar MIDI) y los colores de sus notas. En este fichero los instrumentos se definen de la siguiente forma: <Instrument> <Id>num</Id> <PatchNumber>patchNum</PatchNumber> <BorderColor>R,V,A</BorderColor> <BackgroundColor> R,V,A</BackgroundColor> </Instrument> Donde num es un entero que actúa como identificador del instrumento, patchNum es el número de instrumento en el estándar MIDI (ver Anexo B) y R,V,A son tres números enteros entre 0 y 255 que indican las componentes roja, verde y azul de un color. BorderColor hace referencia al color del borde de las notas y BackgroundColor al color de su interior. Para añadir un nuevo instrumento hay que copiar el fragmento y rellenarlo con los valores deseados. En la Figura 107 se puede ver un ejemplo. 100 <Instruments> <Instrument> <Id>1</Id> <PatchNumber>1</PatchNumber> <BorderColor>141,179,226</BorderColor> <BackgroundColor>198,217,241</BackgroundColor> </Instrument> <Instrument> <Id>2</Id> <PatchNumber>41</PatchNumber> <BorderColor>185,208,140</BorderColor> <BackgroundColor>214,227,188</BackgroundColor> </Instrument> <Instrument> <Id>3</Id> <PatchNumber>117</PatchNumber> <BorderColor>178,161,199</BorderColor> <BackgroundColor>229,223,236</BackgroundColor> </Instrument> </Instruments> Figura 107: Formato de fichero Instruments.xml menuInstrument.xml Si el primer fichero se encargaba de definir los instrumentos, el segundo se encarga de definir los botones que se muestran en el menú de instrumentos. Para cada botón define el identificador y las imágenes que se utilizarán cuando el instrumento esté activo e inactivo. El código que utiliza para incluir botones es el siguiente: <Button> <Id>num</Id> <SelectedImage>imagen1</SelectedImage> <UnselectedImage>imagen2</UnselectedImage> </Button> Donde num es el identificador del botón, imagen1 es la ruta de la imagen que se desea utilizar cuando el botón del instrumento esté seleccionado e imagen2 es la ruta de la imagen que se desea utilizar cuando el botón del instrumento no esté seleccionado. Importante: El valor num que identifica al botón debe ser el mismo que el valor num del instrumento definido en el fichero Instruments.xml. Así es posible relacionar los botones del menú con los diferentes instrumentos. En la Figura 108 se puede ver un ejemplo de fichero menuInstruments.xml. 101 <Menu> <Button> <Id>1</Id> <SelectedImage>piano2.png</SelectedImage> <UnselectedImage>piano1.png</UnselectedImage> </Button> <Button> <Id>2</Id> <SelectedImage>violin2.png</SelectedImage> <UnselectedImage>violin1.png</UnselectedImage> </Button> <Button> <Id>3</Id> <SelectedImage>tambor2.png</SelectedImage> <UnselectedImage>tambor1.png</UnselectedImage> </Button> </Menu> Figura 108: Formato de fichero Instruments.xml 108 109 Anexo C: Ejemplo de uso de PortMIDI A continuación se muestra un ejemplo en el que se utiliza PortMIDI para reproducir una nota con todos los instrumentos disponibles. #include "portmidi.h" #include "porttime.h" #include <stdio.h> #include <time.h> #include <windows.h> #define TIME_PROC ((int32_t (*)(void *)) Pt_Time) #define TIME_INFO NULL #define TIME_START Pt_Start(1, 0, 0) /* Inicialización del Timer */ #define OUTPUT_BUFFER_SIZE 256 #define DRIVER_INFO NULL int dispositivo; int32_t latencia = 0; PmStream * midi; //Descriptor del dispositivo midi abierto PmEvent buffer[2]; //Buffer de eventos midi Figura 113: Cabeceras, definición de constantes y variables. En el primer fragmento de código (Figura 113) se incluyen las cabeceras necesarias para utilizar PortMIDI y algunas otras que permiten utilizar funciones como Sleep(). Seguidamente se realizan algunas definiciones necesarias para el correcto funcionamiento de PortMIDI. En primer lugar TIME_PROC define un puntero a un procedimiento encargado de retornar el tiempo en milisegundos. TIME_INFO es un puntero que se le pasa a TIME_PROC para obtener el tiempo. Seguidamente se define el inicializador del timer mediante la función Pt_Start. El primer argumento de Pt_Start representa la resolución en milisegundos del timer, el segundo es un callback definido por el usuario que se llamará cada X milisegundos, siendo X la resolución del timer. El tercer argumento permite que el usuario introduzca valores que posteriormente se pasarán como parámetros al callback. En el caso de ejemplo la resolución definida para el timer es de un milisegundo, mientras que el resto de argumentos se han dejado nulos. OUTPUT_BUFFER_SIZE especifica el número de eventos que se mantendrán en el buffer mientras se espera para su salida. DRIVER_INFO es un puntero a una estructura que almacenará información relativa al dispositivo abierto. Finalmente se declaran 4 variables. La primera de ellas, dispositivo, almacenará el identificador del dispositivo MIDI seleccionado. Latencia almacenará el retraso, en milisegundos, con el que se reproducirán los mensajes. Midi es la variable que almacenará el descriptor del dispositivo MIDI abierto. Finalmente se declara un buffer de eventos MIDI que será el utilizado para almacenar los mensajes que se quieren enviar. 110 void seleccionarDispositivo(){ /* Mostrar dispositivos MIDI disponibles */ printf("Dispositivos MIDI disponibles:\n\n"); int i; for (i = 0; i < Pm_CountDevices(); i++) { const PmDeviceInfo *info = Pm_GetDeviceInfo(i); printf("%d: %s, %s", i, info->interf, info->name); printf("\n"); } /*Solicitar selección de dispositivo*/ do{ printf("Seleccione un dispositivo: "); scanf("%d",&dispositivo); while(getchar()!='\n'); printf("\n\n"); }while(dispositivo<0 || dispositivo >= i); } Figura 114: Función encargada de la selección del dispositivo. La función seleccionarDispositivo (Figura 114) muestra en primer lugar todos los dispositivos MIDI disponibles por pantalla. Posteriormente se solicita al usuario que seleccione uno de ellos. El identificador del dispositivo seleccionado es almacenado en la variable dispositivo. La Figura 115 muestra el resultado obtenido por pantalla tras ejecutar la función. Figura 115: Selección de dispositivo void abrirDispositivo(){ //Inicializar el Timer TIME_START; //Apertura del dispositivo Pm_OpenOutput(&midi, dispositivo, DRIVER_INFO, OUTPUT_BUFFER_SIZE, TIME_PROC, NULL, latencia); } Figura 116: Función encargada de iniciar la comunicación con el dispositivo. 111 La función abrirDispositivo (Figura 116) inicializa el timer mediante la definición TIME_START y seguidamente se abre el dispositivo. La función Pm_OpenOutput recibe como parámetro el descriptor del dispositivo MIDI (variable midi), el identificador del dispositivo que se desea abrir (variable dispositivo), la estructura DRIVER_INFO para obtener información relativa al driver, OUTPUT_BUFFER_SIZE para indicar el tamaño del buffer de salida, el puntero al procedimiento que retorna el tiempo en milisegundos (TIME_PROC), el puntero que se pasa a TIME_PROC (en este caso nulo) y la latencia en milisegundos. Tras ello se abre el dispositivo y ya es posible enviar mensajes. A continuación se muestra la función principal (Figura 117), la cual se encarga de llamar a las dos funciones de selección y apertura de dispositivo y posteriormente envía los mensajes pertinentes para reproducir las notas. int main( int argc, char *argv[]){ seleccionarDispositivo(); abrirDispositivo(); printf("Dispositivo %d abierto.\n\n",dispositivo); for(int i = 0; i<127; i++){ buffer[0].timestamp = TIME_PROC(TIME_INFO); buffer[0].message = Pm_Message(0xC1, i, 0); Pm_Write(midi, buffer, 1); //Pulsamos la tecla del piano buffer[0].timestamp = TIME_PROC(TIME_INFO); buffer[0].message = Pm_Message(0x91, 60, 127); //Soltamos la tecla del piano buffer[1].timestamp = buffer[0].timestamp+1000; buffer[1].message = Pm_Message(0x81, 60, 0); Pm_Write(midi, buffer, 2); printf("Nota instrumento %d \n\n",i); Sleep(1200); } //Cerrando y terminando cerrarDispositivo(); } Figura 117: Función principal. Tras la selección y apertura del dispositivo la función entra en un bucle for en el que se recorren todos los instrumentos posibles (de 0 a 127). Para cada iteración se envía un mensaje para cambiar el programa, otro para activar la nota y otro para detenerla. Como se puede ver, cada componente del buffer tiene dos registros: timestamp y message. El primero define cuándo se emitirá el mensaje y el segundo define qué hará el sintetizador. 112 buffer[0].timestamp = TIME_PROC(TIME_INFO); buffer[0].message = Pm_Message(0xC1, i, 0); Pm_Write(midi, buffer, 1); En este caso el mensaje se emitirá al instante y se trata de un mensaje de cambio de programa en el canal 1. La variable i define el instrumento. Seguidamente se envía el evento mediante Pm_Write. De la misma forma se procede con el envío de la nota: //Pulsamos la tecla del piano buffer[0].timestamp = TIME_PROC(TIME_INFO); buffer[0].message = Pm_Message(0x91, 60, 127); //Soltamos la tecla del piano buffer[1].timestamp = buffer[0].timestamp+1000; buffer[1].message = Pm_Message(0x81, 60, 0); Pm_Write(midi, buffer, 2); En este caso el segundo evento se emitirá 1000 ms después que el primero, por lo que la nota durará 1 segundo. Finalmente se termina la aplicación ejecutando la función cerrarDispositivo() (Figura 118) que se encarga de cerrar el dispositivo y finalizar la librería PortMIDI. void cerrarDispositivo(){ Pm_Close(midi); Pm_Terminate(); } Figura 118: Función encargada de cerrar el dispositivo. 113 Anexo D: Ejemplo de archivo MIDI El siguiente código (Figura 119) muestra un archivo MIDI de ejemplo en el que se reproducen 10 notas: 4 de ellas por un piano y las 6 restantes por una viola. Cada par de dígitos en hexadecimal representa un byte. 4D 54 68 64 00 00 00 06 00 01 00 02 9E 21 4D 54 72 6B 00 00 00 1B 00 C1 01 8E 35 91 43 7F 87 68 91 43 00 85 61 91 47 7F 87 68 91 47 00 00 FF 2F 00 4D 54 72 6B 00 00 00 25 00 C2 29 9C 10 92 3C 7F 87 68 92 3C 00 89 50 92 3E 7F 87 68 92 3E 00 88 64 92 40 7F 87 68 92 40 00 00 FF 2F 00 Figura 119: Archivo MIDI. Para encontrar la cabecera del archivo debemos buscar los valores "MThd" (0x4D546864), mientras que las diferentes pistas las podemos encontrar buscando los valores "MTrk" (0x4D54726B). En la Figura 120 se muestra el mismo archivo MIDI separando la cabecera y las diferentes pistas. //Cabecera 4D 54 68 64 00 00 00 06 00 01 00 02 9E 21 //Pista 1 4D 54 72 6B 00 00 00 1B 00 C1 01 8E 35 91 43 7F 87 68 91 43 00 85 61 91 47 7F 87 68 91 47 00 00 FF 2F 00 //Pista 2 4D 54 72 6B 00 00 00 25 00 C2 29 9C 10 92 3C 7F 87 68 92 3C 00 89 50 92 3E 7F 87 68 92 3E 00 88 64 92 40 7F 87 68 92 40 00 00 FF 2F 00 Figura 120: Cabecera y pistas del archivo MIDI. Análisis de la cabecera A continuación se explican las diferentes partes de la cabecera: 4D 54 68 64 00 00 00 06 00 01 00 02 9E 21 • Identificador: Como se ha comentado anteriormente, el identificador de la cabecera siempre es 0x4D546864 o "MThd" en ASCII • Tamaño de la cabecera: Puesto que el tamaño del resto de cabecera (Formato + Número de pistas + División de tiempo) no cambia, siempre son 6 bytes. • Formato: El formato indicado es el 1, por lo que en el archivo se disponen dos o más pistas que contienen los eventos. • Número de pistas: En este caso, el número de pistas es 2. Identificador Tamaño de la cabecera Formato Número de pistas División de tiempo 114 • División de tiempo: La división de tiempo es 0x9E21 que en binario es 1001111000100001 El primer bit a 1 indica que la división del tiempo está en formato fotogramas por segundo. Así pues, de los 15 bits restantes, los 7 primeros corresponden con el número de frames por segundo, en este caso 30. El resto de bits indica el número de tiempos delta que tendrá cada frame, en este caso 33. Análisis de la pista A continuación se realiza un análisis de la primera pista. La segunda pista no se analiza ya que esencialmente es lo mismo que la primera. Los diferentes campos de la pista son: 4D 54 72 6B 00 00 00 1B 00 C1 01 8E 35 91 43 7F 87 68 91 43 00 85 61 91 47 7F 87 68 91 47 00 00 FF 2F 00 • Identificador: El identificador de la pista siempre es 0x4D54726B que se corresponde con la cadena ASCII "MTrk". • Tamaño: Indica la cantidad de bytes de la pista, en este caso 27 bytes. • Eventos: Todos los eventos MIDI siguiendo el formato delta time – status byte – data 1 – data 2. Estando delta time en formato variable-length. Ejemplos de eventos: 00 C1 01 El primer byte indica el tiempo delta, en este caso 0. Seguidamente el status byte indica que se trata de un mensaje de cambio de programa destinado al canal uno. El programa al que se cambia es el número 1, que corresponde al piano. El segundo bytes de datos no se utiliza (ver Anexo B). 8E 35 91 43 7F En este segundo ejemplo se puede apreciar mejor cómo el tiempo delta está definido como un variable-length. Si tomamos los dos primeros bytes 0x8E35: 10001110 00110101 El primer bit indica que el valor continúa en el segundo byte. De forma que el delta time final es: 00011100110101, o lo que es lo mismo 1845 tiempos delta. Para pasarlos a tiempo real hay que aplicar la división de tiempo de la cabecera. Identificador Tamaño Eventos Fin de pista 115 1845 ܶ݅݁݉݌݋∆ 33 ܶ݅݁݉݌݋∆ /݂݋ݐ݋݃ݎܽ݉ܽ 30 ݂݋ݐ݋݃ݎܽ݉ܽݏ/ݏ = 1.863 ݏ Es decir, el evento se reproducirá tras transcurrir 1836 milisegundos desde que se produjo el último evento. Seguidamente, tras la especificación delta time encontramos el status byte y los bytes de datos. El status byte indica que el evento se trata de una activación de notas en el canal 1. El primer dato indica que la nota a reproducir es Sol. Finalmente, el último byte indica que la velocidad de pulsación es la máxima (127). • Fin de pista: En realidad también es considerado un evento pero con un significado especial: el fin de la pista. 116 117 Bibliografía 1. Ignacio Sustaeta, Mª Pilar Domínguez-Alcahud. Aplicaciones didácticas de la informática musical. 4, Madrid : s.n., 2004, Vol. 1. 2. Reactable Systems. Reactable. [En línea] [Citado el: 2 de Septiembre de 2010.] http://www.reactable.com/. 3. NUI Group. Diffused Illumination. [En línea] [Citado el: 30 de Agosto de 2010.] http://wiki.nuigroup.com/Diffused_Illumination. 4. Graef, Jürgen y Heuer, Jonas. An interactive music table with tangible notes, that helps students to learn the notation of music. [En línea] Octubre de 2009. [Citado el: 2 de Septiembre de 2010.] http://www.jonasheuer.de/index.php/noteput/. 5. Meyer, Lukas, y otros. Orbi Tone. [En línea] 5 de Febrero de 2010. [Citado el: 2 de Septiembre de 2010.] http://www.orbitone.de/. 6. Downs, Rick. Using resistive touch screens for human/machine interface. Analog Application Journal. s.l. : Texas Instruments Incorporated, 2005. 7. Santum. Stantum. [En línea] [Citado el: 15 de Septiembre de 2010.] http://www.stantum.com. 8. NUI Group. Multitouch Technologies. [En línea] Mayo de 2009. [Citado el: 30 de Agosto de 2010.] http://nuicode.com/attachments/download/115/Multi-Touch_Technologies_v1.01.pdf. 9. Han, Jefferson Y. Low-Cost Multi-Touch Sensing through Frustrated Total Internal Reflection. Proceedings of the 18th Annual ACM Symposium on User Interface Software and Technology. Seattle : s.n., 2005. ISBN: 1-59593-271-2. 10. TUIO Community. TUIO Specification. [En línea] [Citado el: 12 de Septiembre de 2010.] http://www.tuio.org/. 11. NUI Group Community. Community Core Vision. [En línea] [Citado el: 4 de Septiembre de 2010.] http://ccv.nuigroup.com/. 12. Wright, Matt. The Open Sound Control 1.0 Specification. [En línea] [Citado el: 18 de Septiembre de 2010.] http://opensoundcontrol.org/specification. 13. SDL Development Community. Simple DirectMedia Layer. [En línea] [Citado el: 12 de Septiembre de 2010.] http://www.libsdl.org/. 14. Thomason, Lee. TinyXML. [En línea] [Citado el: 13 de Septiembre de 2010.] http://www.grinninglizard.com/tinyxml/. 15. Puig, Sergi Jordà. Audio digital y MIDI. Madrid : Anaya Multimedia, 1997. 16. Sam Lantinga, Stephane Peter y Ryan Gordon. SDL mixer 1.2. [En línea] [Citado el: 21 de Septiembre de 2010.] http://www.libsdl.org/projects/SDL_mixer/. 17. Sonic Spot. Midi File Format. [En línea] 9 de Agosto de 2010. http://www.sonicspot.com/guide/midifiles.html.