jueves, 26 de abril de 2012

Mejoras y resolución de problemas de la aplicación AudioSense


Los objetivos marcados para esta semana son los siguientes:
  • Mejorar el cálculo de las energías medias por barra para el ecualizador, distinguiendo entre voz y música.
  • Distinción entre voz y música para el cálculo del patrón de vibración.
  •  Mejorar el patrón de vibración en lo que se refiere a duración máxima del patrón de vibración.
  • Deshabilitar el giro de pantalla.
  • Solucionar el problema con el mensaje inicial del toogleButton.   
  • Deshabilitar las vibraciones en las diferentes opciones de cierre de la aplicación.
Para mejorar el aspecto del ecualizador gráfico hacemos que la relación no sea lineal, en lo que se refiere a muestras por barra. 

Cuando se trata de música usamos las frecuencias de las notas musicales de la cuarta hasta la séptima octava. Tuvimos que aumentar el número de barras de 16 a 28. Por tanto en el caso de música cada barra contiene la energía media de la frecuencia de la nota musical y de su sostenido, si existe.  

Cuando se trata de voz hacemos uso de la escala Bark, la cual nos da las frecuencias más importantes de la voz. Como no llegábamos a cubrir 28 valores para 28 barras y sabiendo que el espectro más importante de la voz se encuentra entre 0 y 2kHz, añadimos varias frecuencias que creíamos también importantes.

También decidimos mejorar el patrón de vibración, siendo diferente el cálculo en caso de ser voz que en el caso de ser música. Lo que hemos hecho es que si se trata de la voz sólo analizaremos el rango de frecuencias de 0 a 2000Hz ignorando el resto. Esto lo haremos simplemente sumando en la energía total  las 64 primeras muestras en el caso de que sea voz lo que grabamos. También la frecuencia central de máxima energía sólo se estudiará en el rango de 0 a 2kHz. En el caso de música, el cálculo de patrones sigue siendo el mismo al nombrado en anteriores blogs.

A la hora de pensar cómo mejorar los patrones de vibración, se nos ocurrió que podíamos mejorarlos si permitíamos al usuario que decidiese cual era el tiempo máximo que podía durar el patrón de vibración, es decir, que elija entre 1 segundo, 500ms, 200ms y 100ms. Esta opción depende de la apreciación táctil del usuario y la velocidad del hablante con el que desea comunicarse. Lo que hicimos fue definir 4 radioButtons y los unimos en un grupo para que sólo uno de ellos pueda estar activado. Los valores asociados a éstos son los inversos de los tiempos arriba mencionados, es decir, 10Hz, 5Hz, 2Hz o 1Hz. La selección de estos botones depende de lo dicho antes, por tanto si el usuario tiene buena apreciación táctil y la velocidad del hablante con el que desea comunicarse es normal debe elegir 10Hz y si al contrario tiene mala apreciación táctil debe elegir 1Hz y el hablante debe llevar un ritmo lento de habla para no perderse ninguna información.

Para hacer lo anteriormente explicado creamos 4 radio botones y los unimos en un grupo para que solo uno de ellos pueda estar activado. 

La elección de una de las 4 opciones, cambiará ciertos parámetros del código que son: la duración máxima del patrón de vibración y cada cuantas interrupciones del Timer se refrescan los patrones de vibración.

La interfaz gráfica final de la aplicación AudioSense es la siguiente:

 
Después  de todo esto, nos dedicamos a resolver ciertos problemas de la aplicación. Un problema que teníamos era que cuando girábamos la pantalla del móvil la aplicación dejaba de funcionar decidimos,  así que decidimos que la mejor solución era bloquear el giro de pantalla. Para ello en el fichero Manifest.xml de nuestra aplicación android, donde también se definen los permisos, lo que hicimos fue añadir lo siguiente:              android:screenOrientation="nosensor"

Teníamos otro problema con el mensaje/texto inicial asociado al toogleButton, puesto que al iniciar la aplicación en vez de poner Empezar ponía Desconectado, aunque en el resto de pulsaciones de botón los mensajes eran los correctos. Después de intentar encontrar una posible solución de configuración de botones, encontramos otra solución. Esta fue simular una doble pulsación  del toogleButton mediante la instrucción performClick() al inicio de la aplicación, consiguiendo así que mostrará el mensaje inicial correcto. 

Teníamos otro problema que era que las vibraciones no se terminaban siempre, sólo en el uso normal explicado en el manual de usuario. Es decir, que si pulsábamos el botón central, el botón de atrás o sucedía cualquier otro evento del dispositivo móvil ajeno a la aplicación la vibración de nuestra aplicación continuaba.

Esto era un grave problema ya que para  que dejara de vibrar y de grabar teníamos que entrar en las opciones del dispositivo y detener la aplicación desde allí. Lo que necesitábamos es que se finalizará la actividad en esos casos.

Realizando varias pruebas nos dimos cuenta de que al pulsar la tecla de atrás o el botón central o cualquier otro evento del móvil, se ejecuta el onPause y después se fuerza la ejecución del onStop. Por lo que lo que hacemos es definirlos y en el caso de que se esté grabando y vibrando en ese momento, es decir, la variable grabando esté a 1 ponemos la variable de grabando a 0 para que termine todo el proceso en paralelo de grabación y de vibración (al poner grabando a 0 se finaliza el proceso background de AsyncTask). Además en el onStop realizamos el método performClick() el cual hace que se pulse el botón de Terminar, para que cuando volvamos a entrar a la aplicación, ésta esté en su estado inicial.

                Para también arreglar el problema asociado a la pulsación única del botón Salir, como hemos comentado en el apartado de Listeners, forzamos que en su listener además de poner grabando a 0, se realice el onDestroy el cual cancela las vibración, termina la actividad actual mediante el método finish y borra todo espacio de memoria ocupado por el proceso de grabación mediante el método release().

Nuestros próximos objetivos serán:
  • Creación de una aplicación de traducción de mensajes a código Morse.

miércoles, 18 de abril de 2012

Patrón de vibración y ecualizador


Los objetivos marcados para esta semana eran los siguientes:
- Aumentar el ecualizador de 8 barras a 16 barras.
- Quitar el ruido ambiente (30dB).
- Eliminar la componente continua.
- Calcular los patrones de vibración, y asociar la actualización de éstos al Timer.
- Mejorar el aspecto del ecualizador añadiéndole colores según la altura de la barra.

Lo primero que hicimos fue aumentar el número de barras del ecualizador. Para ello lo que hicimos fue en vez de incluir en el cálculo de la energía media 16 muestras por cada barra, fue reducirlo a la media de energías de 8 muestras por barra y distribuir de manera diferente las barras en la pantalla, en vez de tener un ancho de 30 pixeles pasa a 20, y además comienza el dibujo más pegado al borde inferior de la pantalla.

                El siguiente objetivo que nos marcamos fue eliminar la continua, que para ello lo único que hicimos fue anular el término que ocupa la posición 0 del array de energías. Esto lo hicimos ya que la continua no aporta nada en nuestro estudio. También eliminamos de la representación del ecualizador el ruido ambiente restándole 30 dB a la energía media de cada barra ya calculada, si la barra tenía una altura menor de 30dB, automáticamente se ponía a 0.

                Para que se diferenciasen más las alturas de cada barra, multiplicamos por un factor mayor que la unidad la energía media de cada barra, en particular, un factor de 2,5. Observamos que para que el ecualizador aportará más información gráfica era necesario restarle más dB de ruido ambiente por lo que al final dejamos que el ruido ambiente era alrededor de 120dB, dada la alta sensibilidad del micrófono. 

                Lo siguiente que hicimos fue calcular el patrón de vibración. Lo hicimos mediante un método sencillo el cual debemos probar más su funcionalidad. El patrón de vibración se va a calcular cada 100ms, por lo que un patrón se repetirá durante 100ms siempre, ya que la modificación de los valores del patrón por ahora va asociado al mismo Timer que el refrescar el ecualizador gráfico. El cálculo se lleva a cabo con la frecuencia  a la que la energía es máxima y con la energía total. Para calcular el tiempo que durará el patrón lo hacemos con la frecuencia de mayor energía, haciendo una relación lineal. Imponemos que la duración mínima sea 10ms y la máxima 100ms, y lo relacionamos con nuestras posibles frecuencias (0-4000Hz) de máxima energía, siendo 10ms cuando la frecuencia sea 4000Hz y 100ms cuando la frecuencia se 0Hz. Para calcular el ciclo de trabajo de dicho patrón lo hacemos mediante la energía total en dB, imponiendo un máximo de 360 dB y un mínimo de 300 dB haciendo una relación lineal. Por ejemplo si la energía total es de 360dB, el ciclo de trabajo será del 100%  y si la energía total es de 300 dB o inferior será un ciclo de trabajo nulo, consiguiendo que el ruido del entorno no se traduzca a vibraciones, solo se traduzca la voz "cercana" al micrófono. 

                Uno de los problemas que nos encontramos en este momento era que cuando dábamos a parar la aplicación, y salíamos de ella, no paraba de vibrar el dispositivo, hasta que no se forzaba el cierre completo de la aplicación. Esto lo solucionamos añadiendo en el botón de salir una igualdad a null del objeto Vibrate.
                Una vez funcionaba todo lo anterior comenzamos a mejorar el aspecto del ecualizador. En un primer momento todas las barras eran del mismo color independientemente de la altura, lo que implementamos fue que las barras se dividiesen en cuadrados de 20 pixeles cada uno de un color que iba cambiando gradualmente con respecto al cuadrado anterior y posterior, así la barra según la altura a la que llegaba tenía unos colores u otros.  Este cambio gradual de cambio de color lo hicimos definiendo un array de constantes en los que poníamos los colores para después configurarlos a través de “Color.rgb(red, green, blue)”, y añadiéndolos a un objeto de la clase Paint. Nos surgieron una serie de problemas a la hora de implementar estas mejoras ya que configurábamos mal alguna variable, y nos dibujaba solo una barra, o incluso una barra en diagonal. Después de arreglar estos pequeños problemas el aspecto final de la aplicación es el siguiente. 


            
   
Hemos añadido un botón para que el usuario pueda seleccionar si lo que quiere “reconocer” es voz, sino será música pero queda implementarlo destacando en cada caso unas frecuencias diferentes del espectro.

Los siguientes objetivos que nos marcamos fueron:
- Mejorar el patrón de vibración.
- Hacer que la resolución del ecualizador no sea lineal, es decir, diferenciar si el sonido captado del entorno es voz o música, ya que habrá que destacar diferentes frecuencias de trabajo.
- Deshabilitar el giro de pantalla.


jueves, 12 de abril de 2012

Resolución de problemas


Para este “hito” nos plantemos los siguientes objetivos:

- “Dar la vuelta” al ecualizador gráfico.
- Corregir el error de la fft, explicado en el blog anterior.
- Comprobar el correcto funcionamiento de la fft (y un poco de todo el programa)
mediante la respuesta activa del ecualizador gráfico.

Comenzamos “dando la vuelta” al ecualizador. Para ello lo que hicimos fue calcular el número
de pixeles que tiene la pantalla del dispositivo android disponibles para pintar, es decir, sin
contar los pixeles dedicados a los botones. A partir de ese valor calculamos la nueva esquina
superior del cuadrado, restándole a éste la longitud del rectángulo a pintar (la energía media
de la banda de frecuencias) empezándose así a dibujar las barras del ecualizador desde arriba.
El resto de parámetros permanecieron iguales. Este primer objetivo nos costó poco resolverlo.

El siguiente paso fue más complicado ya que no entendíamos porque la primera fft que se
realizaba nos daba valores correctos (valores en el rango de lo esperado) pero las siguientes
fft, el valor real de la fft de cada muestra crecía exponencialmente con valores negativos hasta
al final desbordar el float. Hicimos diferentes pruebas para reconocer que era lo que causaba
el error. La prueba que nos hizo descubrir cuál era el error, fue realizar la fft repetidas veces al
mismo array de muestras grabadas, sin darnos cuenta de que el método de la fft nos devolvía
la fft en ese mismo array, y observamos que ocurría lo mismo arriba explicado. Por tanto,
deducimos que lo que pasaba en nuestro caso era que no cambiaba el valor del array de las
muestras grabadas inicialmente, el cual introducimos como parámetro al método, sino que
siempre era la mismo, ya que el código de realiza fft era con lenguaje C y utilizaba punteros,
y al volver a llamar al método de la fft se quedaba con el array anterior introducido como
parámetro el cual contienía ahora la fft, realizándose así la fft de la fft de la ftt…de la muestra
incial grabada, sin utilizar el resto de muestras lo que era nuestro propósito.

Para solucionarlo, lo que hicimos fue crear el array de muestras grabadas dentro del
propio “while” donde se realiza todo el cálculo de la fft, energías y frecuencias y cambiar el
archivo C para que en vez de usar punteros a arrays usara esos mismos arrays. Ahora si nos
daba valores coherentes pero la aplicación cerraba alrededor de los 10 segundos sin previo
aviso. Pensamos que era problema de tiempo de ejecución como por ejemplo que no le daba
tiempo al método a realizar todos los cálculos antes de coger la siguiente muestra. Probamos
diversas soluciones como: cambiar las dimensiones de los arrays, comentar los códigos
cuyo tiempo de ejecución pudiese ser mayor… Después de todas estas pruebas nos dimos
cuenta de que el problema de que se cerrase la aplicación (sin previo aviso) no era de falta de
tiempo sino de falta de memoria. El dispositivo al quedarse sin memoria disponible cerraba la
aplicación. Este exceso de “gasto” de memoria era porque en cada vuelta de while se creaba
un nuevo array de muestras. Investigamos distintos métodos para solucionar este apartado
pero al final lo que hicimos fue asignar el valor null al array al final del código del while y luego
liberar memoria para que así lo eliminase con la instrucción: “System.gc();”.

Una vez hecho esto, el programa funcionaba sin ningún problema por tiempo indefinido, y
pudimos comprobar mediante sonidos que introducíamos en el micrófono del dispositivo
android que todo el proceso (incluido la fft) era correcto, porque las barras del ecualizador
gráfico se movían coherentemente a los sonidos.



El aspecto de la aplicación es el siguiente:




Una vez conseguidos los objetivos arriba mencionados y que funcionará todo, nos propusimos los siguientes objetivos:
-          - Calcular los parámetros de vibración, para ya llevar a cabo la acción final de vibrar con ellos.
-          - Ver qué frecuencia de vibración puede reconocer nuestro sentido táctil.
-          - Mejorar las barras del ecualizador gráfico: quitar la continua, quitar el ruido(30 dB), escalar el resto de dB para que se aprecie mayor cambio de energías en las bandas, colores…


miércoles, 11 de abril de 2012

Ecualizador Gráfico y Timer


El siguiente paso que nos propusimos fue realizar el ecualizador gráfico. Estos fueron los objetivos de este paso:
-          - Aprender a dibujar en la pantalla de nuestro programa.
-          - Hacer que apareciese en la pantalla el ecualizador gráfico, que representa lo grabado en tiempo real.
-          - Construir un Timer para poder “refrescar” los valores  del ecualizador gráfico cada 50ms.

Para poder dibujar en la pantalla de nuestra aplicación necesitábamos aprender a usar la clase Canvas, en particular, el método onDraw() de Canvas. Para hacerlo creamos la clase VisualizerView (subclase de la clase View) y implementamos dentro de ésta el método onDraw(Canvas canvas). La implementación de éste método por ahora era muy sencilla, sólo intentamos pintar un cuadrado con el método drawRect(). El “último” paso para ver si habíamos aprendido a pintar un cuadrado en la pantalla, fue ejecutar el método onDraw()en el método onCreate()(el cual se ejecuta al arranque de la aplicación).

Una vez hecho todo esto, nos surgió un problema ya que intentábamos en una misma pantalla tanto poner los botones con la ayuda del eclipse (con el archivo main.xml) como pintar un cuadrado en ésta mediante la programación arriba descrita. Este problema lo que producía era un conflicto de “quien manda sobre la pantalla” y nos mandaba forzar cierre nada más intentar arrancar la aplicación.

Para solucionarlo lo que tuvimos que hacer fue crear toda la interfaz del programa sin usar la ayuda del eclipse, es decir, tuvimos que crear también los botones mediante programación sin ayuda del simulador, definiendo su tamaño, su situación en pantalla… Una vez hecho esto, el problema arriba mencionado fue resuelto.

Ya aparecido el cuadrado en pantalla, aprendimos a configurar diferentes parámetros de dibujo como: color (clase Paint), colocación en una situación específica de la pantalla, largo y ancho de un rectángulo para dibujar nuestras barras del ecualizador…
  
El siguiente paso era aprender a crear el Timer, para que poder refrescar el ecualizador gráfico cada 50ms. Para crear el Timer había que programar un método run() para que pasado el  periodo que hayamos configurado gracias al método Schedule() se ejecute el método run(). En ese método lo único que ejecutaremos fue  el  método updateVisualizer(), que lo único que hace es actualizar el dibujo. Este fue el código:
t = new Timer();
                           scanTask = new TimerTask() {
public void run() {
                                        mVisualizerView.updateVisualizer();
                                                   }
                           };
                           t.schedule(scanTask, 0, 50);

Al ejecutar el programa en este momento nos daba un error y nos obligaba a forzar cierre de nuevo. Buscando el error nos dimos cuenta de que el Timer se ejecutaba en un “hilo” paralelo al propio hilo donde se ejecutaba el resto del programa, y el error se debía a que el hilo propio no permitía que el hilo paralelo del Timer dibujase en “su” pantalla.

Para solucionar este problema vimos que era necesario crear un Handler, junto con un Runnable para que el Timer se ejecute en el hilo principal y así se pudiese actualizar la pantalla mediante éste, quedando el siguiente código:
t = new Timer();
scanTask = new TimerTask() {
public void run() {
             handler.post(new Runnable() {
                    public void run() {
                           mVisualizerView.updateVisualizer();                                     }
             });
      }};
t.schedule(scanTask, 0, 50);

Tras haber conseguido dibujar un cuadrado (y aprender a configurar más parámetros de dibujo) y que funcionase el Timer, probamos ambas funcionalidades dibujando un rectángulo que fuera aumentando cada segundo un pixel de largo, de esta manera comprobamos que funcionaban ambas a la perfección, y el programa estaba listo para “pintar” el ecualizador gráfico.

Empezamos a crear el ecualizador asociado al Timer, y el siguiente problema que nos encontramos fue que no sabíamos como dibujar de abajo hacia arriba ya que al crear un LinearLayout te obliga a empezar el siguiente objeto siempre debajo del anterior tomando como origen de coordenadas la esquina superior izquierda de la pantalla. La solución que propusimos de manera provisional fue crear el ecualizador al revés, para ello creamos ocho rectángulos (barras del ecualizador gráfico) que fueran variando con la energía media por rango de frecuencias.

Al intentar comprobar la funcionalidad de la fft (explicada en el blog anterior) mediante la visualización del ecualizador nos dimos cuenta que la fft no funcionaba bien, ya que después de dos o tres  segundos el valor de las muestras toman valores negativos y de gran valor, por lo tanto la energía daba valores muy grandes y llegaba un momento que las variables se desbordan y daban 0, e incluso los pixeles de la pantalla no soportaban esos valores, por lo que al pasar unos segundos ya no dibuja el ecualizador.

Después de todo esto, los objetivos que nos hemos marcado para la siguiente semana son:
- “Dar la vuelta” al ecualizador gráfico
- Solucionar el problema de la fft.
- Comprobar la funcionalidad de la fft por respuesta del ecualizador gráfico.