Archivo de la categoría: Cacharreo

Problemas tecladiles (8)

He empezado a añadir funcionalidades extra a mi teclado. En concreto, controles multimedia (volumen, etc). Y ya he encontrado un bug raro: sólo funcionan si la placa está en modo «keyboard+mouse+joystick», pero no en modo «serial+keyboard+mouse+joystick».

Las placas Teensy se pueden poner en varios modos desde el entorno de Arduino. Aquí los podemos ver:

Lo normal es utilizar el modo «Keyboard + Mouse + Joystick» para productos finales, y el modo «Serial + Keyboard + Mouse + Joystick» para depuración, pues podemos enviar texto a la consola. Sin embargo, por algún motivo, si la placa está en este último modo, los códigos de las teclas multimedia (KEY_MEDIA_VOLUME_INC, KEY_MEDIA_VOLUME_DEC, etc) no funcionan: no se recibe absolutamente nada (y lo he comprobado leyendo directamente de /dev/input/eventXX).

Problemas tecladiles (7)

¡¡Hace unos días me han llegado las placas de PCBWay!! Pero como estuve fuera, hasta hoy no he podido poner nada.

Se trata de la versión 1.17 del teclado, que frente al diseño 1.0 de los teclados que construí hasta ahora, tiene tres NeoPixels, huella para el protector ESD, correcciones de compatibilidad con Teensy, serigrafía en los pines de programación, y algunos otros detallitos menores (como el maravilloso recuadro blanco para escribir anotaciones). Y el acabado es, sencillamente, perfecto. ¡Un 10 para PCBWay!

Después de la experiencia que he tenido, creo que estos teclados los voy a construir con pulsadores Cherry «de verdad». Iré contando.

Problemas tecladiles (5)

Recientemente estuve haciendo algunos cambios extra en el diseño. Por ejemplo, combiné en ambos diseños la tecla SHIFT corta + tecla de «menor y mayor» de las distribuciones ISO, junto con la tecla SHIFT larga de las distribuciones ANSI. Esto permite combinar distribuciones, algo que puede ser interesante para aquellos que prefieran la tecla de RETURN americana, pero quieran usar una distribución española, por ejemplo.

También aproveché para añadir dos NeoPixeles más en los dos huecos que había entre F4 y F5, y F8 y F9, lo que permite aumentar las posibilidades.

Por supuesto, llegó también el momento de diseñar la carcasa, para lo que decidí aprovechar los resultados de un cursillo de diseño e impresión 3D que hice hace tiempo (¡¡¡¡Gracias por convencerme para ir, Miguel!!!!). No tendría que ser demasiado difícil…

El primer problema que me encontré fue que el teclado mide 32cm de ancho, pero las impresoras 3D normales tienen una cama de entre 20 y 30cm, lo que significa que no podía imprimirlo todo de una sola vez sino que tendría que dividir la carcasa en dos mitades que se pudiesen pegar. Ante esto comencé con el primer diseño:

Como base no estaba mal, pero faltaban las patas y cubrir los huecos entre las teclas. Para ello se me ocurrió que podía utilizar una pieza que se añadiese por detrás.

Menos mal que se me ocurrió probar a imprimirla antes… ¡Menudo desastre! Se suponía que la parte delantera, que es la que queda hacia el fondo en la imagen, tenía unos huecos en donde la placa se introducía para quedar fijada; por desgracia, para imprimirlos hacía falta utilizar soportes, los cuales no había manera de quitar en una zona tan estrecha como una placa de circuito impreso. Ante esto, decidí probar un segundo diseño:

En este nuevo diseño, el bloque principal de la carcasa estaba compuesto por cuatro piezas en lugar de dos (a mayores hay dos piezas extra para hacer una pata que va de un extremo a otro). Las dos piezas traseras se imprimirían «planas», pero las delanteras, en donde estaba la ranura en donde encajaría la PCB, se imprimiría «hacia arriba», con el frontal apoyado en la cama. De esta manera se podían evitar los soportes.

Por desgracia, aún era demasiado complicado, pues la parte de la pata consumía muchísimo filamento y no era nada sencilla de montar. Además, la alineación de las cubiertas para las zonas entre las teclas era crítica, pues hay menos de un milímetro de margen: un error, por pequeño que sea, haría que las teclas rozasen o, peor aún, no pudiesen bajar… como de hecho me ocurrió.

Finalmente, tras darle muchas vueltas se me ocurrió que podía simplificarlo muchísimo si separaba las cubiertas de los huecos de la carcasa, de manera que fuesen piezas independientes. De aquí salió el diseño de arriba: las dos piezas traseras se imprimen rotadas 90 grados, mientras que las dos piezas delanteras se imprimen «tal cual», de manera que no hace falta ningún tipo de soporte. El diseño lo hice sin la base para ahorrar plástico, pues quería usar un trozo de lámina de polietileno recortada para abaratar costes y reducir el tiempo de impresión.

Las piezas para cubrir los huecos entre las teclas de función las imprimí aparte, con la intención de pegarlas directamente en la PCB. Esto no sólo simplificaba mucho el diseño, pues no había que hacer malabares para evitar tener que usar soportes, sino que, además, eliminaba completamente los problemas de falta de precisión:

El diseño tenía buena pinta, pero aún no me acababa de convencer, pues había que pegar cuatro piezas para construir la carcasa, lo que no era muy cómodo. Además, la idea de ahorrar costes utilizando una lámina de polietileno como base no resultó: para empezar, las ranuras que había en el diseño para que ésta encajase eran demasiado estrechas, algo que, además, dependería de la impresora en sí. Pero además, si quería que las piezas se imprimiesen bien, no me quedaba más remedio que imprimir una «balsa», con lo que el ahorro no era tal, pues al final estaba imprimiendo la base igual, sólo que la tiraba a la basura.

Finalmente, llegué al diseño actual, que es éste:

La carcasa en sí está compuesta por solo dos piezas, las cuales, además, son muy fáciles de pegar. La superficie principal de la base tiene un grosor de sólo 0,6 milímetros, lo que significa que se gasta la misma cantidad de filamento que si se utilizase una «balsa» con el diseño anterior; sin embargo, la resistencia es la misma, pues en los bordes el grosor aumenta para darle rigidez. Las patas se imprimen aparte y se pegan en el borde trasero. Esto tiene la ventaja de que es posible imprimir patas de distinta altura para ajustarlo a las preferencias de cada uno. Por último, las cubiertas se imprimen aparte, como se ve, y además hay tres piezas extra que también se pegan entre las teclas y que sirven como «tuercas» para atornillar la placa a la carcasa (aunque en los dos teclados que hice no fue necesario, pues quedaron perfectamente ajustadas).

Y el resultado final es éste:

Documentando con los pies (2)

En la entrada anterior expliqué cómo construí un pedal para tomar fotografías desde el móvil y así simplificar el documentar proyectos hardware. Por desgracia, cuando llegó el momento de usarlo por primera vez me encontré con un problema: resulta que la aplicación de cámara de Android se cierra automáticamente al cabo de dos minutos de no hacer nada con ella. Esto es un problema porque entre foto y foto bien puede pasar mucho más tiempo, lo que me obligaría a acercarme al móvil y lanzar la aplicación de nuevo. Esto no tiene nada que ver con el apagado automático de la pantalla: aunque le ponga media hora para que apague la pantalla, la aplicación de la cámara vuelve a la pantalla principal a los dos minutos de estar «sin hacer nada».

Obviamente esto es completamente inaceptable, así que decidí agarrar el toro por los cuernos y escribir una pequeña aplicación de cámara para Android que no tuviese este problema. Para ello partí de un ejemplo de cómo utilizar la cámara en Android, escrito en Kotlin. Reconozco que no es un mal lenguaje, pero no le acabo de pillar el punto (todo lo contrario que Go… pero esa es otra historia).

Tras una primera prueba, conseguí que funcionase de manera constante. Sin embargo, me encontré con la desagradable sorpresa de que el tener la cámara funcionando constantemente hace que el móvil se caliente un montón, cosa que no me hacía nada de gracia (de hecho, al buscar por qué la aplicación oficial de cámara se cerraba a los dos minutos, uno de los motivos que decía era ese; me parecía muy extraño, teniendo en cuenta que sí puedes grabar vídeo durante más tiempo, pero visto lo visto, podría ser una de las razones).

Ante esto, tuve que complicar un poco la aplicación, haciendo que la cámara se desactive al cabo de un tiempo sin hacer nada, pero dejando la aplicación en primer plano y activando de nuevo la cámara tan pronto se pulsa uno de los botones de volumen para tomar una foto. De esta manera el móvil no se recalienta.

Como comienzo no estuvo mal, pero claramente tenía un problema: ¿cómo puedo saber si una foto es buena, o tengo que repetirla? La solución fue relativamente sencilla: añadir la posibilidad de enviar cada foto tomada a mi portátil, que estaría en un sitio donde podría ver fácilmente el resultado. Así, añadí dos campos de texto en la app en los que teclear la dirección IP del portátil y un puerto (por defecto usa el 9000), y en éste correr una pequeña aplicación en python que se limite a escuchar por dicho puerto, y cuando se abra, recibir los datos de la imagen en formato JPEG, descomprimirlos, y mostrarlos en la pantalla.

El resultado era bastante bueno, pero tenía un problema aún: la cámara de mi móvil es de 4160×3120 pixels, un tamaño exageradamente grande que resulta en ficheros muy grandes. El resultado es que entre que pulso el pedal y aparece la foto en el portátil pasan unos nueve segundos (tres de los cuales son debidos a tener que encender la cámara). Para resolverlo añadí un campo de «resolución», que permite poner un tamaño deseado (yo le pongo 2048), de manera que el móvil escogerá la resolución más cercana a ese valor entre los que permita la cámara. Con esto, el tiempo baja a cuatro segundos si la cámara estaba apagada, y un segundo si la cámara estaba encendida (por ejemplo si no nos gusta la foto y decidimos sacar otra, la cámara estará ya encendida y será muchísimo más rápido).

El código fuente (compilable con Android Studio) está disponible en mi repositorio GIT: https://gitlab.com/rastersoft/footcam. Hay un paquete APK para Android ya compilado que se puede descargar desde la sección Tags, simplemente pinchando en el número de versión. En el repositorio principal se puede descargar el programa de visualización remota, llamado «receiver.py«. Para utilizarlo, además de Python3 es necesario WxPython.

Documentando con los pies

Me gusta hacer proyectos de electrónica, pero documentarlos… ¡ay! Eso ya es otro cantar. A fin de cuentas, para ello tengo que decidir cuando sacar alguna foto, y eso me obliga a parar lo que estoy haciendo, sacar el móvil, encuadrar con una mano mientras con la otra sostengo lo que estoy haciendo… un cristo.

Pero a la vez tengo claro que quiero documentarlo, por un lado por mí, porque así tengo un registro de las cosas que he hecho y me puede servir en el futuro («¿Cómo había hecho aquello…?), y también porque puede ser útil para otras personas.

Tras darle varias vueltas, se me ocurrió una idea: dado que tengo un trípode con soporte para el móvil, podría montarlo y encuadrar una zona concreta de la mesa de trabajo, ya con luz adecuada y demás, y cada vez que quiera sacar una foto sólo tendría que pulsar un botón.

Por supuesto, queda la cuestión de cómo conectar ese botón, pero afortunadamente es algo que hace cualquier palo selfie que se precie, por lo que sólo tenía que conectarlo a la entrada de cascos. Rebusqué en internet y encontré las especificaciones oficiales 3.5 mm Headset: Accessory Specification y 3.5 mm Headset Jack: Device Specification. Estas dos especificaciones indican cómo se deben conectar los altavoces, micrófono y pulsadores en un móvil Android, y qué funcionalidad debe tener cada uno.

Una lectura rápida nos indica que los botones están conectados entre el terminal 3 y 4 del jack, en paralelo con el micrófono, y que las cuatro funcionalidades posibles se consiguen presentando un valor concreto de resistencia entre dichos terminales. Así, si aparecen 0 ohmios (o sea, si se cortocircuitan), la función es la A (play/pausa/descolgar si es una pulsación corta; asistente si es una pulsación larga, o siguiente canción si son dos pulsaciones cortas); con un valor de entre 210 y 290 ohmios la función será la B (subir volumen); un valor de entre 360 y 680 ohmios activará la función C (bajar volumen); por último, un valor entre 110 y 180 ohmios activará la función D, que es una funcionalidad «reservada». Dado que la cámara se puede activar con cualquiera de los botones de volumen, bastaba con poner una resistencia adecuada en serie con un pulsador entre los terminales 3 y 4 del jack y estaría listo.

Decidí hacer una prueba rápida con un único botón, y para mi sorpresa… ¡¡¡no funcionó!!! Ante esto, decidí hacer un montaje un poco más completo con las funciones A, B y C:

Con los valores del esquema, el botón A cortocircuita los pines 3 y 4 del jack; el botón B muestra 240 ohmios, y el C 480 ohmios.

Y seguía sin funcionar: el móvil detectaba que había algo conectado, pero no hacía absolutamente nada: ni subir y bajar el volumen, ni responder a una llamada… ¡nada! Revisé los valores de resistencia con el polímetro, probé otros valores para activar otras funciones… pero nada, no quería funcionar. Era rarísimo.

Decidí buscar a gente que hubiese hecho un montaje similar y encontré un par de ejemplos documentados, pero ambos hacían exactamente lo mismo que yo.

Me releí una y otra vez la documentación, y entonces caí en un detalle: se indica que el micrófono tiene que tener una impedancia en continua superior a 1000 ohmios. Yo había dado por supuesto que podía dejarlo «desconectado», pues infinito es mayor que 1000 ohmios, pero, por si acaso, decidí poner en paralelo una resistencia de 2Kohmios, así:

¡Y ahora sí que funcionó! Está claro que al menos mi móvil espera que haya una cierta impedancia, y no un valor «infinito».

Con esto claro, hice algunos cálculos y el circuito definitivo es éste:

Cuando el pulsador está abierto, el móvil ve una resistencia de 2 Kohmios, y cuando se pulsa, al poner en paralelo un valor de 320 ohmios, el valor que ve es de 1 / (1/2000 + 1/320) = 275,8 ohmios, lo que activa la función B (subir volumen).

Ahora llegó el momento de decidir cómo activar la cámara. Aunque la primera opción sería tener un pulsador en algún lugar cómodo, eso seguiría implicando soltar lo que esté haciendo, así que decidí que tenía que ser algo que pudiese activar sin las manos. Y la opción obvia es un pedal. Aunque podía comprar uno, preferí aprovechar cosas que ya tenía por aquí y montarme uno yo mismo. Para ello utilicé unos pulsadores de circuito impreso que tenía en casa y unas protoboards.

Los cuatro pulsadores están conectados en paralelo, así que basta con que se active cualquiera para que el móvil detecte la función «subir volumen». Obviamente, la placa va al revés de como se ve, con los pulsadores hacia el suelo, y hago presión por el lado de las soldaduras… algo nada recomendable (ni para las soldaduras, ni para el pie si se hace descalzo), así que le pegué una segunda placa por el lado opuesto para taparlo todo.

Y este es el resultado:

Problemas tecladiles (4)

EDITADO: actualizados los fuses.

Como ya comenté, decidí reemplazar el cargador original del Atmega32U4, llamado DFU y que se puede descargar desde la página de Atmega, por Ubaboot, un bootloader reducido que sólo ocupa 512 bytes y que es muy sencillo de utilizar. A mayores envié dos parches para simplificarlo aún más:

Para instalar Ubaboot, lo primero que hay que hacer es editar el fichero config.h y configurar los parámetros de nuestra placa. En el caso de mi teclado, hay que descomentar OSC_MHZ_16 (pues el reloj es de 16MHz), y USB_REGULATOR:

Una vez hecho esto, hay que programar los fuses del microcontrolador para un bootloader de 512bytes, además de asegurarnos de configurar bien el resto. En el caso de mi teclado, la configuración es la siguiente:

Fuse LOW: 0x5E

  • CKDIV8 0
  • CKOUT 1
  • SUT1/0 = 0x01
  • CKSEL3/0 = 0x0E

En este bloque básicamente no tocamos nada.

Fuse HIGH: 0xDE

  • OCDEN 1 (desactivado)
  • JTAGEN 1 (JTAG desactivado)
  • SPIEN 0 (programación por SPI activada)
  • WDTON 1 (watchdog desactivado)
  • EESAVE 1 (preservar EEPROM al borrar la memoria)
  • BOOTSZ1/0 0x11 (bootloader de 512 bytes)
  • BOOTRST 0 (vector de reset apunta al bootloader)

En este bloque básicamente desactivamos el JTAG, pues no lo necesitamos para nada, ponemos la dirección de inicio del cargador a 512 bytes antes del final de la memoria, y por último configuramos el vector de reset para que salte al cargador directamente. Esto último es muy importante, pues el propio cargador detecta si estamos arrancando «en frío» (en cuyo caso saltará a nuestro código directamente) o si hemos pulsado el botón de reset (en cuyo caso entrará en modo programación).

Fuse EXTRA: 0xFE

  • RESERVADO 7/4 tienen que estar todos a 1 (0xF)
  • HWBE 1
  • BODLEVEL2/0 0x1 (EDITADO)

El entorno de Arduino me había cambiado el BODLEVEL a 110 (1,8 a 2,2 voltios), en lugar del original 011 (2,4 a 2,8 voltios). Tras algunas pruebas, y en base a mi experiencia, he decidido que es más seguro ponerlo a 001 (3,3 a 3,7 voltios). Esto es algo que he cambiado en este artículo después de haberlo publicado.

El siguiente paso es conectar un programador SPI al teclado a través de los pines correspondientes. Yo uso un BusPirate que me prestaron, para lo cual tuve que editar el fichero Makefile y sustituir usbtiny -B10 por buspirate -P /dev/ttyUSB0 -b 115200, además de instalar AVRdude y el resto de utilidades de AVR. Con esto ya sólo me queda conectarlo al teclado y ya puedo leer el estado con

avrdude -c buspirate -P /dev/ttyUSB0 -b 115200 -p m32u4

borrar todo el estado para desprotegerlo con

avrdude -c buspirate -P /dev/ttyUSB0 -b 115200 -p m32u4 -e

grabar los fusibles con

avrdude -c buspirate -P /dev/ttyUSB0 -b 115200 -p m32u4 -U efuse:w:0xFE:m
avrdude -c buspirate -P /dev/ttyUSB0 -b 115200 -p m32u4 -U hfuse:w:0xDE:m
avrdude -c buspirate -P /dev/ttyUSB0 -b 115200 -p m32u4 -U lfuse:w:0x5E:m

y hacer make y make program para grabar el Ubaboot. Con él grabado, sólo tengo que pulsar el boton de RESET del teclado y se pone automáticamente en modo programación por USB, con lo que ya no necesito más el BusPirate.

Y ya, por último, instalé Arduino y Teensyduino para disponer de las bibliotecas de USB. Sin embargo, hice un pequeño cambio para integrar la programación mejor. Los cambios concretos están descritos en el fichero README del segundo parche que envié a Ubaboot.

Y con esto escribí la primera versión del software de mi teclado, pero eso lo comentaré con calma en otra entrada.

Cargando mis cascos (y 3)

Hace unos años escribí una entrada explicando cómo modifiqué mis cascos inalámbricos para que se cargasen sólo con colgarlos de un soporte. Hace algo menos de tiempo publiqué un nuevo diseño, con un mejor acabado pero aún algo chapucero. Hoy ha llegado el momento de mostrar el diseño de-fi-ni-ti-vo.

Este es el diseño en FreeCAD, el cual está disponible en mi gitlab:

Si lo comparamos con el diseño anterior, éste está mucho más pulido porque incluye las lengüetas de carga y agujeros para pasar los cables. También tiene una ranura por debajo, de manera que los cables quedan completamente ocultos.

El diseño, además, está completamente acotado, por lo que es muy sencillo ajustarlo para otros tamaños de cascos. Así, la diadema de mis cascos mide 35 milímetros de ancho, por lo que la separación entre las lengüetas está ajustada a 33 milímetros, de manera que se garantiza que siempre hará contacto al colgarlos. Pero si queremos utilizarlo con otros cascos, sólo tenemos que editar esa medida para ajustarlo al valor necesario.

Así quedó tras imprimirlo en un tono anaranjado que hace juego con la madera del mueble donde va a ir atornillado, y tras pegarle cinta adhesiva de cobre en las lengüetas para hacerlas conductoras:

El siguiente paso fue meter los cables del transformador por cada uno de los agujeros, y soldarlos al cobre. La mejor manera de hacerlo es estañar primero el punto en el que vamos a soldar el cable, cortar el cable a la longitud correcta, estañar la punta, meterlo por el agujero, y simplemente con un pequeño toque del soldador, unir ambas partes. Así evitaremos recalentar demasiado el plástico y que se derrita o deforme.

Por último, añadimos un poco de cola térmica en la guía de la base para que el cable quede firme y evitar que un tirón lo arranque de las lengüetas:

¡Y ya está! Éste es el resultado final:

Daisy, Daisy…

Recientemente, Alva Majo, desarrollador de videojuegos indie y youtuber, publicó un vídeo donde explicaba cómo creó Alvabot, un sintetizador de habla que utilizaba muestras de su propia voz. Como, además, hizo público el código fuente y las muestras de voz, decidí que podía ser divertido intentar llevarlo un poco más allá y hacer que cantase. ¡Y lo he conseguido!

Sí, vale… el resultado no es exactamente profesional, pero para ser algo hecho en un par de días, creo que no está mal.

El código y las muestras de cantabot están disponibles en mi repositorio GIT bajo una licencia MIT/Expat (que viene a ser equivalente al «haz lo que te de la gana» con la que distribuyó Alva Majo su código original). También están disponibles las pequeñas utilidades que usé para reconstruir las muestras y adaptarlas para la tarea.

El proceso fue algo alambicado, pero al final no tiene mucha ciencia. Obviamente, si hubiese querido conseguir mejores resultados tendría que haber utilizado otras técnicas, pero la idea era hacer sólo un pequeño divertimento.

En primer lugar escribí una pequeña herramienta en Python (analizador.py, disponible en el repositorio) que me permite ver la forma de onda de un fichero WAV, escoger un trozo entre dos puntos concretos, calcular la frecuencia de dicha onda, y cortar entre los máximos de dicho trozo (todo esto lo explicaré mejor a continuación). Gracias a que Python tiene módulos para todo, no necesité programar casi nada, sino que ha sido más una operación «de pegar cosas».

Así, tomé los samples de las vocales y utilicé la herramienta anterior para revisar la forma de onda de cada una (en este artículo denominaré sample a cada fichero de audio con el sonido de una vocal o una consonante; y utilizaré el término muestra para referirme a cada uno de los datos de sonido que hay dentro de cada fichero; esto es: cada «número» dentro del fichero .WAV será una muestra).

Aquí vemos una ampliación de la vocal A, tal y como se ve en la herramienta:

Si nos fijamos, vemos que parece que hay cierta «repetición». O sea: la onda no es «aleatoria», sino que parece que hay un bloque que se repite en el tiempo (como se puede apreciar por esos picos espaciados de manera regular). Es verdad que la amplitud (la altura) no es constante, pero eso es simplemente porque Alva es un ser humano, y por mucho empeño que ponga es imposible que mantenga exactamente la misma intensidad durante toda la vocal, sino que inevitablemente subirá y bajará un poco en el tiempo.

Para verlo mejor, ampliemos un trozo:

Aquí ya se ve mucho mejor cómo la vocal es, en realidad, un trocito de sonido que se repite una y otra vez. En esta imagen concreta lo vemos repetido cinco veces. Esto significa que si creamos un sample con sólo ese trozo, podremos repetirlo en bucle y conseguir que el ordenador pronuncie una vocal durante todo el tiempo que queramos, sin que se noten cortes, ligaduras ni nada por el estilo. Esto es fundamental a la hora de cantar porque cada nota puede tener una duración diferente a las demás, y son justo las vocales las que se «estiran» en el tiempo para cubrir todo el tiempo que dura una nota.

Aquí es donde mi herramienta analiza.py me pide dos puntos en la gráfica. El primero tiene que estar un poco ANTES de uno de los máximos, y el segundo un poco DESPUÉS del siguiente máximo. Entonces, la herramienta busca hacia adelante el valor máximo local desde el primer punto, y hacia atrás desde el segundo punto, para así recortar con precisión el trozo periódico. Para la vocal A utilicé 1668 y 2023, marcados en rojo en la imagen, y que como vemos caen justo cerca de dos máximos consecutivos. El propio programa busca los máximos, los cuales están en 1697 y 2001.

Lo segundo que hice fue calcular la frecuencia fundamental de cada vocal. Para los que no sepan a qué me refiero, la frecuencia fundamental determina la «nota» en la que está sonando la vocal. Cuando mayor sea la frecuencia, más alta en la escala será la nota. Al principio hice algunas pruebas con la transformada de Fourier, hasta que me di cuenta de que no necesitaba complicarlo tanto, pues simplemente con saber el número de muestras que hay en el «trozo» repetitivo y la frecuencia de muestreo, ya puedo obtener la frecuencia fundamental del sample.

Así, por ejemplo, si decidimos utilizar para la vocal A el bloque situado entre los picos de las muestras 1697 y 2001, vemos que hay un total de 304 muestras en él, lo que significa que si dividimos la frecuencia de muestreo de este fichero .WAV (que es de 44100 muestras/segundo) entre dicho valor, nos dará que la frecuencia a la que Alva pronunció la letra A es de 145 Hz.

Repitiendo este proceso para las cinco vocales, sale que cada una fue pronunciada con estas frecuencias:

vocal A: muestras 1697 a 2001, 145 Hz
vocal E: muestras 555 a 855, 147 Hz
vocal I: muestras 2968 a 3242, 161 Hz
vocal O: muestras 2246 a 2544, 148 Hz
vocal U: muestras 3027 a 3317, 152 Hz

Esto demuestra que Alva Majo NO es un robot, pues no es capaz de afinar correctamente. Por eso cada vocal tiene una frecuencia diferente.

Pero claro, para nosotros esto es un problema porque si queremos que el ordenador cante, necesitamos que las muestras estén todas en la misma frecuencia. Si no, lo único que conseguiremos es un ordenador que desafina.

Afortunadamente, una vez que tenemos ya aislado el bloque periódico de cada vocal, ajustar su frecuencia es muy sencillo: sólo necesitamos hacer que todas midan lo mismo (pues la frecuencia fundamental depende del tamaño del bloque y de la frecuencia de muestreo; como la frecuencia de muestreo tiene que ser la misma para todos los samples, tenemos que conseguir que el número de muestras sea igual). Si calculamos la media de todas esas frecuencias, nos salen 150 Hz, así que ajustaremos todas a ese valor para evitar tener que desplazarlas demasiado. Y si ahora dividimos la tasa de muestreo entre esa cifra, nos sale que cada bloque debe tener 44100/150 = 294 muestras. Por supuesto, no se trata de añadir, por ejemplo, ceros al final, porque entonces estaríamos modificando la señal. Lo que necesitamos es «inventarnos» muestras en medio para estirarla si es demasiado corta, o «tirar a la basura» muestras si es demasiado larga. Por supuesto, si añadimos muestras éstas deben «encajar» con las que las rodean; y si quitamos muestras, debemos hacerlo de manera «repartida». Esto, como no, tiene un nombre: remuestreo o cambio de tasa de muestreo, y el módulo scipy incluye una función llamada resample() que lo hace. Así que en la parte final de la herramienta hago una llamada a dicha función para ajustar cada sample justo antes de grabarlo en disco.

Para poder evaluar los resultados, podemos comparar el antes:

con el después:

Ahora llega el momento de hacer lo mismo con las consonantes, pero esto es un poco más complicado, pues éstas no tienen por qué tener un patrón (aunque algunas sí), por lo que será difícil calcular su frecuencia… o no, porque, afortunadamente, Alva grabó todas las consonantes seguidas de todas las vocales. No tenemos sólo el fonema ‘P’, sino que también tenemos los sonidos de ‘PA’, ‘PE’, ‘PI’, ‘PO’ y ‘PU’, así que podemos medir la frecuencia a la que pronunció la vocal y asumir que es la misma que la del fonema de la consonante. Luego sólo tendremos que cortar justo en el punto donde termina la consonante y empieza la vocal, y tendremos el sonido puro de la consonante. Veamos un ejemplo con la sílaba «DA»:

Para hacer esto escribí otra pequeña herramienta, cortar_consonantes.py, la cual carga un fichero .WAV, muestra su forma de onda para que podamos buscar dos muestras que delimiten un periodo de la vocal, y nos pide que tecleemos su posición. Con ellas calcula la frecuencia a la que está pronunciada y también cuanto tiene que estirar o encoger todo el fichero WAV para ajustarlo a los 150 Hz que decidimos que sería nuestra frecuencia central.

Una vez que está ajustada, nos pide el número de muestra en la que realizar el corte para separar la consonante de la vocal. Como no es fácil acertar, lo que hice es que, tras dar un punto de corte, reproduzca la consonante seguida de la vocal «I» (para lo cual, utilizo los samples que generé antes). De esta manera, si se recorta demasiado poco y se cuela parte de la vocal original, sonará AIIIIIII en lugar de IIIIIII. Así podremos ir ajustando hasta encontrar el punto óptimo de cada consonante. Cuando hayamos encontrado el punto exacto, simplemente pulsamos RETURN en lugar de meter un nuevo número y el programa grabará el nuevo sample con sólo la consonante, ya ajustada a 150 Hz.

Algunas consonantes, pese a todo, son sonoras, lo que significa que SI están formadas por una zona repetitiva. En concreto, para la B, la M y la N utilicé el mismo sistema que para las vocales, pues se entendían mucho mejor que extrayéndolas «tal cual» de los samples.

Haciendo que hable

Ahora que ya tenemos los sonidos vocales y consonantes aislados y a la misma frecuencia, podemos hacer una primera prueba que se limite a hablar. Para ello escribí hablar.py, que generará un fichero habla.wav en el que pronunciará la frase que se le pase por la propia línea de comandos.

Lo primero que hace este programa es sustituir las letras por fonemas, de manera que no hace falta pensar en sonidos sino que podemos escribir directamente una frase. Además, si alguna vocal tiene tilde la pronunciará a una frecuencia ligeramente superior, lo que permite simular la entonación de las frases. En teoría se podría hacer que fuese completamente automático, y que si una palabra no tiene tilde, compruebe si acaba en n, s o vocal y, en base a ello, determine si es aguda o llana y ponga la tilde automáticamente, pero no me apeteció ponerme con ello, así que hay que hacerlo a mano.

Ejemplo: «Hóla, sóy cantabót hablándo.»

Y haciendo que cante

Y por último, está la herramienta final, cantar.py, que genera un fichero .WAV con una canción generada con las muestras.

El programa se compone de una clase con dos métodos fundamentales: interpreta() y pausa(). Estos dos métodos se deben ir llamando para introducir la partitura y la letra de la canción. Cada llamada renderiza los sonidos correspondientes, que se van añadiendo a un buffer interno. Cuando la canción está lista, se llama al método grabar(), que lo almacenará en disco en formato WAV con el nombre canta.wav.

El método pausa es el más sencillo, y simplemente recibe como parámetro un número decimal con el número de segundos de silencio que se deben añadir al buffer. Permite añadir silencios entre notas.

El método interpreta es el más complejo, y recibe tres parámetros:

  • Un entero con la octava de la nota a interpretar. Este valor estará entre 0 y 12 e indica el semitono concreto. La correspondencia exacta está en el código fuente
  • Un valor decimal con la duración en segundos de la nota.
  • Un string con los fonemas a cantar en dicha nota

Para simplificar la composición, se definen seis variables (redonda, redondadot, blanca, blancadot, negra y negradot) con las duraciones en segundos. Se establece la duración de la redonda en 1,5 segundos, y el valor de la blanca y la negra queda definido automáticamente como la mitad y la cuarta parte de la redonda. A mayores, las duraciones con «dot» son un 50% más que la correspondiente sin «dot». Además, se definen también las variables nota_XX, con el valor de semitono correspondiente a cada nota (por ejemplo, nota_sol vale 5).

Por otro lado, a la hora de decidir cuanto tiempo dura cada fonema de la nota se siguen las siguientes reglas:

  • Las consonantes durarán lo que dure su sample, salvo las consonantes «sonoras», que durarán 0,2 segundos
  • Las vocales que no sean la última durarán 0,15 segundos
  • La última vocal durará el tiempo necesario para completar la duración de la nota

Así, la última vocal durará realmente la duración de la nota menos la duración del resto de fonemas. Este valor se calcula automáticamente, lo que simplifica mucho meter una partitura.

Dado que ya nos preocupamos antes de ajustar todos los samples para que estuviesen a 150 Hz, ahora lo único que tenemos que hacer es ajustar la frecuencia de todos los fonemas a la necesaria para la nota. Y lo interesante es que para saber cuanto tenemos que ajustar la frecuencia, sólo necesitamos elevar la raíz doceava de 2 al valor del semitono que le pasamos a la función (por supuesto, tras restar 5, pues por convenio he decidido que los samples estarán en SOL).

¿Y por qué la raíz doceava de 2? En este vídeo de Minute Physics lo explican muy bien, pero para simplificar: como la frecuencia de una nota cualquiera tiene que ser la mitad de la frecuencia de la misma nota en la siguiente octava, y cada octava son doce semitonos, la única manera de que esto encaje es que la frecuencia de cada semitono sea la frecuencia del semitono anterior multiplicado por la raíz doceava de dos. Así que si quiero hacer sonar un LA (semitono 7 como vemos en la tabla de arriba), necesito subir la frecuencia de los fonemas en dos semitonos, o sea, multiplicar dos veces por la raíz doceava de dos. En cambio, si quiero interpretar un MI, tengo que bajar la frecuencia en tres semitonos; o sea, dividir tres veces entre la raíz doceava de dos.

¿Y cómo cambiamos la frecuencia de los fonemas? Pues exactamente como vimos antes: interpolando para estirarlos o encogerlos. Si los estiramos, la frecuencia baja; y si los encojemos, la frecuencia sube. Por tanto, una vez hemos calculado el factor entre la frecuencia inicial y la final, sólo tenemos que coger el número de muestras que tiene cada fonema y multiplicarlo por dicho factor, obteniendo así el nuevo número de muestras que tiene que tener. Con ese número llamamos a interpolate() para que haga su magia, y añadimos las muestras al buffer.

A ritmo de conga (17)

Desde que tengo mi robot aspirador, tengo que tener cuidado de dejar la puerta de la cocina cerrada cuando la uso, porque obviamente no es buena idea que se te cuele en un sitio donde siempre puede haber algo de grasa que se te haya caído, o así. Pero es un rollo tener que estar pendiente, así que decidí buscar una solución.

Lo primero que se me ocurrió fue comprar cinta adhesiva magnética, pues se supone que este modelo tiene un sensor, y cuando detecta un campo magnético asume que no tiene que entrar por ahí. Por desgracia, no funcionaba: la aspiradora pasaba por encima como si no estuviese.

Ante esto decidí buscar una alternativa, así que se me ocurrió que podía meter un relé reed conectado al sensor del parachoques, de manera que cuando pasase por encima de la cinta, el robot pensase que ha chocado. Así que aprovechando que se le acabó la garantía, decidí abrirla y echar un vistazo a ver si podía hacerlo.

Lo primero que me encontré es que mi suposición de que llevaba dos micros, el principal por un lado, y un ESP8266 (o ESP32, no fui capaz de verlo bien) conectado por un puerto serie, era acertada.

La segunda sorpresa fue descubrir que en el frontal tiene un sensor de efecto Hall.

Claro, esto era muy raro, porque se supone que estos sensores son, entre otras muchas cosas, para detectar cintas magnéticas en el suelo. La conclusión que saqué es que está capado por software, y está ahí porque al fabricante le sale más barato fabricar todas iguales y luego afinarlo mediante código.

También vi que los sensores del parachoques no eran microswitches, sino optointerruptores. El motivo supongo que será fiabilidad.

El siguiente paso fue comprar unos cuantos relés reed y hacer varias pruebas a ver qué tal tiraban, pues temía que tuviesen que estar demasiado cerca de la cinta para funcionar. La primera sorpresa fue que funcionan mucho mejor en perpendicular a la cinta magnética que en paralelo, hasta el punto de que en paralelo hay que pegar prácticamente el relé a la cinta para que funcione, pero en perpendicular se activa ya a unos dos/tres centímetros.

Pero claro, no puedo garantizar que el relé siempre esté perpendicular, pues si la aspiradora va de frente hacia la cinta magnética, estará en una posición, mientras que si va siguiendo el borde de la pared, estará girado 90 grados…

Al final, lo resolví poniendo dos relés reed en cada lado, conectados en paralelo y a 90 grados uno del otro. De esa manera siempre funcionará de manera óptima.

La conexión sería bastante sencilla: bastaría con conectarlos en paralelo con el sensor óptico del parachoques:

De esta manera, cada vez que la aspiradora se acerque a la cinta magnética del suelo, creerá que ha chocado contra la pared.

La siguiente cuestión era… ¿donde los monto? La primera idea fue pegarlos directamente por debajo, pero luego me di cuenta de que los relés reed son de vidrio y muy frágiles, por lo que no era muy recomendable. Por otro lado, el interior del aspirador está muy lleno, y no queda casi sitio. Al final, después de darle varias vueltas y de considerar incluso ponerlos debajo de las trampillas de los motores de las ruedas, decidí que la tapa de la batería era el mejor lugar, sobre todo porque la batería deja suficiente hueco a cada lado. Así que procedí a pegar los dos grupos de relés por dentro de la tapa con pegamento térmico:

Una vez montados ambos pares de relés, procedí a pasar los cables por el mismo agujero por el que pasan los de la batería:

Por último, procedí a soldar los cables a los sensores. MUY IMPORTANTE NO EQUIVOCARSE, y conectar cada par de relés al sensor óptico del mismo lado, pues si no, la aspiradora girará hacia el lado opuesto y acabará quedándose atrapada sobre la línea.

Y con esto se acabó, y ahora mi aspiradora ya no se cuela en la cocina.

A ritmo de conga (16)

Hace unos días me encontré con un problema raro: mi aspiradora no respondía a mis órdenes en la app. Pero lo mas raro era el estado de los botones: estaba cargando, pero el botón de «volver a casa» estaba activo, y el de «empezar a limpiar» estaba desactivado.

Un vistazo rápido a los logs me permitió descubrir que el estado actual de la aspiradora era «10», y antes había pasado por el «9» y por el «7». Esto era muy raro, pues los únicos estados que conocía hasta ahora iban desde el «1» (limpiando), hasta el «6» (en la base, batería cargada), tal y como comenté en la entrada «A ritmo de conga (6)».

Obviamente esto era muy raro, así que empecé a investigar revisando los logs, y descubrí qué ya me había ocurrido lo mismo en dos ocasiones más. Las preguntas obvias eran: ¿por qué? y ¿Qué significaban esos nuevos estados?

Entonces me fijé en un detalle raro: antes de pasar al modo «9», la aspiradora estaba limpiando (modo «1»), pero la batería estaba muy baja. De pronto se puso en modo «9», y unos segundos después pasó a modo «10», y entonces el nivel de la batería, de pronto, empezó a subir. Y cuando llegó al 100%, volvió a modo «1», o limpiar.

La respuesta era obvia: cuando la aspiradora está limpiando y ve que la batería está demasiado baja como para terminar el trabajo, ella sola se vuelve a la base (modo «9») para cargarla de nuevo (modo «10») y continuar la operación donde la dejó. El «7» todavía no se qué puede ser. A lo mejor tiene que ver con un par de veces que se me quedó atascada por culpa de un reflejo del sol (no se por qué, pero si da el sol en el parqué, el sensor antiescaleras se lía), pero tengo que hacer más pruebas para confirmarlo.

Así, la lista de estados posibles pasa a ser:

  • 1: limpiando
  • 2: detenida
  • 4: regresando a la base
  • 5: en la base, cargando la batería
  • 6: en la base, batería cargada
  • 7: ??????
  • 9: regresando a la base por batería baja
  • 10: en la base, cargando batería, para continuar un trabajo previo interrumpido por batería baja