This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Proyecto OpenPipe: Diseño Conceptual de un Controlador MIDI
09 de Abril de 2007 (primera versión pública, por completar)
En pocas palabras, y definido de forma sencilla, un controlador MIDI no es más
que un dispositivo lógico (implementado en software) o un dispositivo físico
(implementado en hardware) que produce mensajes MIDI a partir de la
ocurrencia de una serie de eventos producidos por la interacción con un
usuario.
¿Qué es un evento? Un evento no es más que un determinado hecho que ha
ocurrido o que ocurrirá en un determinado instante del tiempo. Normalmente,
para nosotros (como programadores) la ocurrencia de un evento tendrá
asociada la ejecución de un determinada acción.
Por ejemplo, para una aplicación visual (es decir, con interfaz gráfica), que el
usuario haga clic sobre un determinado botón puede significar un evento.
Probablemente, cuando ocurra el evento “el usuario ha hecho clic sobre el
botón X”, desearemos ejecutar una determinada porción de nuestro código.
Por tanto, en la programación visual es bastante normal programar de forma
orientada a eventos.
El concepto de “evento” es intencionadamente poco concreto, puesto que en
principio, cualquier hecho puede ser potencialmente un evento. ¿Cuál es la
frontera entre un hecho y un evento? Como hemos comentado anteriormente,
un evento tiene un significado especial para nosotros, pues estamos
interesados en saber en qué momento tiene lugar su ocurrencia, para actuar
en consecuencia. Es el propio programador quien define qué eventos quiere
controlar, y como cabría esperar, son específicos para cada aplicación.
En el caso de un controlador MIDI, un evento es cualquier tipo de hecho
relacionado con la interpretación de un músico, cuya ocurrencia sea relevante
para la posterior ejecución del sonido por parte de un sintetizador. Por ejemplo,
si deseamos crear un controlador MIDI con apariencia de piano, un posible
evento sería la pulsación por parte del pianista de una tecla, ya que
posiblemente querremos iniciar la ejecución de una nota cuando el pianista
pulse una tecla. Como cabría esperar, que un pianista deje de pulsar una tecla
también podría ser un evento, éste asociado con la finalización de la ejecución
de la nota anteriormente iniciada.
Sin embargo, mientras tengamos medios para detectar un evento, podemos
definir el número que nosotros queramos. La idea es determinar qué eventos
son importantes de cara a simular el comportamiento de un instrumento real,
lo cual depende del instrumento en cuestión. Por ejemplo, en el modelo del
piano, sería interesante que la intensidad del sonido fuese proporcional a la
intensidad con que el pianista ha pulsado la tecla.
Lo anterior no sería válido si en vez de diseñar un controlador para un piano,
quisiésemos diseñar un controlador para un violín o para una flauta. Para el
caso de un controlador MIDI con apariencia de violín, estaríamos interesados
en saber en qué momento el músico frota y deja de frotar una cuerda con el
arco, lo cual constituirían sendos eventos. Para el caso de un controlador MIDI
con apariencia de flauta, estaríamos interesados en saber qué agujeros está
tapando y destapando en un determinado instante, lo cual constituirían el
evento principal.
De todas formas, aunque los eventos que estamos monitorizando en cada caso
sean completamente distintos entre sí (¿verdad que no es lo mismo pulsar una
tecla que frotar una cuerda?), podemos seguir hablando de evento como un
hecho abstracto. Al fin y al cabo, aunque sean eventos distintos, tanto al pulsar
una tecla en un piano como al frotar una cuerda en un violín implican acciones
similares: Inicio de ejecución de una nota.
Capturando Eventos
Ahora viene el problema, ¿cómo capturamos los eventos? En el caso de un
controlador software el problema es trivial, ya que son los propios lenguajes de
programación los encargados de capturar el evento. El programador no tiene
más que proporcionar una implementación para la función de callback que el
lenguaje de programación invocará cuando se produzca el evento deseado. Por
ejemplo, en el caso de Java:
JButton boton = new JButton(“Púlsame”);boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {/* Aquí iría la acción asociada al evento */System.out.println(“¡Me han pulsado!”);
}});
En el caso de un controlador hardware, el problema es más complicado, ya que
necesitamos dispositivos electromecánicos que sean capaces de detectar el
evento deseado. En el ejemplo del piano, necesitamos que, de alguna forma, la
tecla nos proporcione información del estado de su pulsación por medio de
señales eléctricas. Si además queremos conocer la intensidad con que la tecla
ha sido pulsada, la tecla es quien ha de proveer dicha información mediante
otra señal eléctrica.
Para capturar eventos, podemos utilizar cualquier tipo de dispositivo que nos
imaginemos. La complejidad puede ser tan grande o tan pequeña como
nosotros queramos, siempre al alcance de nuestras posibilidades. No habría
ningún problema en utilizar pulsadores o interruptores convencionales. Ahora
bien, a la hora de interpretar melodías, ¡notaremos que nuestro diseño, aún
siendo correcto, es poco práctico!
Se recomienda consultar los catálogos de tiendas de electrónica por internet.
Hay bastantes posibilidades de encontrar sensores u otros dispositivos a partir
de los cuales podamos capturar los eventos que deseamos. ¡Es cuestión de
tener paciencia y afán de investigación!
Capturando eventos en OpenPipe
Como seguramente muchos ya sabréis, el objetivo del proyecto OpenPipe es
proporcionar el diseño de un controlador MIDI con apariencia de flauta o gaita.
Todo lo comentado hasta ahora también es válido para OpenPipe, pero
aplicado al caso particular de los flautistas o gaiteiros.
¿Cuál es el evento más importante a detectar en éste tipo de vientos? Pues sin
duda, es indispensable conocer qué agujeros están tapados o destapados en
un determinado instante. Grosso modo, el flautista toca diversas notas tapando
unos agujeros u otros. La flauta, como muchos otros instrumentos de viento,
tiene una serie de “posturas” asociadas para cada nota que puede tocar. El
conjunto de todas las “posturas” que puede tocar un instrumento constituye la
digitación.
Para el caso de la flauta, existen dos digitaciones alternativas: La digitación
alemana, y la digitación barroca. Con ambas digitaciones se pueden conseguir
el mismo número de notas, lo que sucede es que, en determinadas notas, la
“postura” asociada es distinta. Quien lo notará, principalmente, es el
intérprete. De cara al espectador que escucha, no habrá prácticamente
diferencia (¡a menos que sea tan ávido como para llevar cuenta de los agujeros
que tapa y destapa, lo cual personalmente me parece improbable!).
Durante algún tiempo estuve buscando por la red algún tipo de sensor que
fuese capaz de detectar la pulsación de un dedo, no de forma mecánica (no un
pulsador), sino de forma sensitiva. Mirándolo desde el punto de vista del
intérprete, lo más cómodo es que el “sensor” en cuestión cambie de estado
simplemente con posar el dedo encima, y nunca pulsando un elemento
mecánico, ya que resultaría en un aparato engorro de utilizar.
Después de una búsqueda incesante, sin ningún resultado positivo, decidí
pedirle ayuda al que será mi tutor en el proyecto fin de carrera [1]. Cuando
entendió lo que quería hacer (relativo al “sensor”), me propuso un circuito tan
sencillo como el siguiente:
Esquema propuesto del “sensor” que capturará los eventos.
Los que tengan conocimientos de electrónica, sabrán que es un circuito
bastante sencillo. No es más que un generador de tensión conectado a una
impedancia (o resistencia). La idea es la siguiente: Nuestro cuerpo también
genera internamente electricidad. Si tocamos con nuestra mano un punto
cualquiera del circuito mostrado en la figura, introducimos una pequeña
componente de corriente, de unos pocos miliamperios (la cantidad aproximada
habría que estimarla experimentalmente). Si además la resistencia es lo
suficientemente grande, la corriente que circula por el circuito es pequeña
(recordemos la fórmula de Ohm V = IR), por lo que llegará un momento en el
que la corriente introducida por una persona al tocar el circuito será lo
suficientemente significativa como para detectar cuándo se está tocando el
circuito, y cuándo no.
En definitiva, con los valores de tensión y resistencia adecuados, cuando una
persona toque el circuito, se producirá un aumento significativo en la tensión
debido principalmente a la corriente producida en el interior del cuerpo de la
persona. Ahora bien, dicho valor de tensión no es utilizable directamente, pues
contiene un valor impreciso. Suponiendo conocidos los umbrales de tensión
ideales (determinados de forma empírica) para detectar “pulsación” / “no
pulsación”, la señal debería pasar previamente por un comparador que,
teniendo en cuenta dichos umbrales, generaría finalmente los valores de
tensión correspondientes a los valores lógicos “alto” y “bajo” utilizados por el
resto de la circuitería (5 voltios y 0 voltios; o 3’3 voltios y 0 voltios; etc.).
Una vez que ya hemos definido cómo se construiría un “sensor”, podemos
juntar varios para hacer nuestro instrumento.
Esquema Conceptual de la OpenPipe
En la figura se puede observar que se han utilizado ocho “sensores”. Cada uno
de ellos se correspondería con un agujero en una flauta convencional. Se ha
añadido uno adicionalmente, para utilizarlo como “sensor” de control.
Lo más normal es que distribuyamos los nueve “sensores” anteriores sobre un
tubo cilíndrico, para imitar la forma de una flauta o una gaita. En principio, el
material del tubo cilíndrico podría ser plástico o incluso madera. Los sensores
estarían “incrustados” literalmente en el tubo, y no sería más que un pequeño
cilindro metálico en el que se introduciría la componente de corriente generada
por el cuerpo del intérprete.
Los “sensores” irían incrustados en el tubo.
Ahora bien, el resto de la circuitería correspondiente a cada uno de los
sensores estaría en la unidad de procesamiento, junto al microcontrolador y al
resto de componentes que conforman la lógica de OpenPipe.
Diseño de la Lógica del Controlador MIDI
Hasta ahora no se ha comentado nada relativo al procesamiento de datos. Nos
hemos limitado a detectar eventos, y a generar señales eléctricas (en principio
digitales, aunque también podrían ser analógicas) en respuesta a esos eventos.
Sin embargo, no hemos de olvidar que nuestro Controlador MIDI debe generar
mensajes MIDI como respuesta a los eventos que se han producido.
¿Por qué tipo de interfaz enviaremos la salida? Porque no es lo mismo enviar
datos a través del puerto USB que a través de una tarjeta de Red. Como cabría
esperar, a la salida tendremos una interfaz MIDI, que será la que transmita
nuestros datos a otro dispositivo MIDI externo, probablemente un sintetizador.
En caso de querer conectar OpenPipe al ordenador, es posible encontrar
interfaces MIDI – USB a partir de 50 euros en tiendas especializadas. También
existen opciones más económicas, pero más en desuso, como podría ser
interfaces MIDI – Puerto de Juegos, en caso de que la tarjeta de sonido
disponga de tal puerto.
En todo caso, a pesar de la gran cantidad de opciones de interconexión que
existen actualmente, es conveniente diseñar OpenPipe para que trabaje con
interfaces MIDI. La razón es muy sencilla: La gran mayoría de dispositivos
musicales electrónicos siguen utilizando el protocolo MIDI como estándar de
facto, aunque existan alternativas más modernas al respecto. En cualquier
caso, siempre queda la posibilidad de utilizar cables conversores y / o hardware
y software especializado para buscar la combinación deseada.
Entonces ya tenemos claro que nuestro diseño lógico: (a) Por un lado, tendrá
como entrada una serie de señales digitales que contienen información sobre
la ocurrencia de determinados eventos, y (b) Por otro lado, deberá producir
como salida información MIDI a través de una interfaz de dicho tipo.
Quienes hayan estado trabajando en temas relacionados con la electrónica,
sabrán que en cuanto es necesario “procesar” una pequeña cantidad de
información, se suele utilizar un componente denominado microcontrolador.
Microcontroladores y Lógica de Control
¿Qué es un microcontrolador? Pues a grandes rasgos, un microcontrolador es
como un microprocesador, en el sentido que tiene capacidad de procesamiento
(puede ejecutar instrucciones de un programa) pero más limitado, puesto que
normalmente se utilizan en actividades de control dentro de otros dispositivos,
o incluso en procesos industriales (donde su uso es más habitual).
Las actividades de control suelen ser rutinarias, y se caracterizan por necesitar
una cierta cantidad de procesamiento de datos a bajo nivel. Los
microcontroladores suelen incorporar una memoria interna de programa, en la
que se puede almacenar el programa de control, una memoria de datos, para
almacenar datos y variables, así como puertos de entrada / salida (tanto
analógicos como digitales) que permiten el flujo de datos con el exterior.
La arquitectura del microcontrolador es específica para cada aplicación.
Podemos encontrarnos microcontroladores con más o menos capacidad de
memoria, con más o menos puertos de entrada / salida. Incluso es habitual que
los microcontroladores incorporen algún tipo de periférico, como puede ser un
puerto serie, un puerto paralelo, un puerto USB, una tarjeta de red, etc. Todo
depende del uso que queramos darle.
En nuestro caso, hablaremos sobre el microcontrolador 8051. Se podrían haber
escogido muchos otros microcontroladores, siendo igualmente válida la
elección. Sin embargo, uno de los puntos a favor del 8051 es que es un
microcontrolador considerado actualmente como un “estándar”, muchos
fabricantes tienen en su catálogo diversos microcontroladores compatibles con
la familia 8051, como por ejemplo Phillips y Samsung.
La arquitectura “tradicional” del 8051 está formada por los siguientes
elementos:
● Arquitectura Harvard (existen espacios de direcciones separados para
código y datos).
● CPU de 8 bits.
● Procesador booleano (operaciones sobre bits).
● Permite direccionar hasta 64 KB de ROM externa y 64 KB de RAM.
● Dos contadores / temporizadores.
● 128 o 256 bytes de memoria RAM interna (16 bytes de los cuales son
direccionables a nivel de bit).
● 4 KB de memoria ROM interna.
● Cuatro puertos de entrada / salida de ocho bits.
● Una UART.
● Cinco fuentes de interrupción (dos externas, dos para los temporizadores
y una para el puerto serie).
● Oscilador interno.
Sin embargo, como se trata de un diseño conceptual (a alto nivel), no se
profundizará mucho más sobre la arquitectura concreta del microcontrolador.
Simplemente se esbozará cómo el microcontrolador es capaz de controlar la
lógica del funcionamiento de la OpenPipe.
Lógica de Funcionamiento de un Controlador MIDI
A partir de los nueve sensores indicados anteriormente, obtendremos nueve
señales binarias, de valor uno o cero. Podemos considerar que un valor de uno
es “agujero tapado”, y que un valor de cero es “agujero destapado”, aunque
sería sencillo implementar una lógica alternativa.
En principio, mientras el usuario mantiene una combinación de agujeros
tapados / destapados, se supone que el instrumento ha de mantener la nota
actual. En el momento que se destape o se tape un nuevo agujero, habrá que
silenciar la nota anterior (es decir, enviar un mensaje NOTE_OFF(notaId) de la
nota que estaba sonando hasta ese momento) y generar una nueva nota (es
decir, enviar un mensaje NOTE_ON(notaId) de la nota que se corresponda con
la nueva postura).
¿Cómo se podría implementar lo anterior? Pues con un bucle que comprobaría
continuamente el estado actual de los “sensores” (notar que el estado de los
sensores viene determinado por el valor de la señal eléctrica correspondiente)
con un estado de referencia (que se correspondería con la última nota
generada). En el momento que el estado actual no fuese exactamente igual al
estado de referencia, apagaríamos la nota actual y generaríamos la nueva
nota, puesto que un cambio en el estado de los “sensores” implica,
necesariamente, un cambio de postura por parte del usuario, lo cual implica, a
su vez, un cambio de nota.
Por tanto, es necesario tener almacenado en una serie de variables el valor
anterior de la señal, para ser capaces de detectar un cambio de estado en
cualquier instante de tiempo. La encuesta se haría de forma continua, ya que
el aparato tiene que ser capaz de responder en tiempo real a los eventos
producidos por parte del usuario.
001002003004005
006007008
// Encuesta el estado actual de los sensoresestadoAnteriorSensor0 = estadoSensor0 = detectarEstadoSensor0();estadoAnteriorSensor1 = estadoSensor1 = detectarEstadoSensor1();…estadoAnteriorSensor8 = estadoSensor8 = detectarEstadoSensor8();eventoNoteON(estadoSensor0, estadoSensor1, …, estadoSensor8);
// Encuesta del estado actual de los sensoresestadoSensor0 = detectarEstadoSensor0();estadoSensor1 = detectarEstadoSensor1();…estadoSensor8 = detectarEstadoSensor8();
}
//// El siguiente código se ejecuta si, y sólo si, el estado// actual no es igual al estado anterior, lo que significa// que el usuario ha cambiado su postura//
// Silenciamos la nota anterioreventoNoteOFF(estadoAnteriorSensor0, estadoAnteriorSensor1, …,
estadoAnteriorSensor8);// Iniciamos la ejecución de la nueva notaeventoNoteON(estadoSensor0, estadoSensor1, …, estadoSensor8);
//// El estado actual pasa a ser el estado anterior//estadoAnteriorSensor0 = estadoSensor0;estadoAnteriorSensor1 = estadoSensor1;…estadoAnteriorSensor8 = estadoSensor8;
}
Figura: Programa de Control de OpenPipe
En la figura se ha escrito en pseudocódigo el programa de control de OpenPipe.
Las líneas de la 1 a la 4 detectan el estado actual de cada uno de los nueve
sensores (numerados desde el cero al ocho). La pseudofunción
detectarEstadoSensor0() detecta el estado del sensor 0, que estará conectado
a una de las señales de algún puerto de entrada salida. En la práctica, lo
habitual es consultar el estado de un bit de algún registro especial del
microcontrolador, como sucede en el 8051.
Se han utilizado nueve variables de un bit para almacenar el estado actual de
cada uno de los nueve sensores. El valor de dichas variables se actualizará de
forma constante mediante encuesta, como se puede comprobar en las líneas
de la 10 a la 13. Además, se utilizan otras nueve variables de un bit para
almacenar el estado anterior de cada uno de los nueve sensores, que también
se inicializan en las líneas 1 a 4. El valor de dichas variables se utiliza como
valores de referencia para detectar los cambios de postura por parte del
usuario.
Las líneas 7 a 14 mantienen el programa en espera activa mientras no se
produce un cambio de postura. En el momento que el estado actual no es el
mismo que el estado de referencia (entendiendo un estado como un valor
determinado por el conjunto de valores de los sensores en un instante
arbitrario), se pasa a ejecutar las instrucciones de las líneas 15 y 16, que