scieee AI-readable full text Open interactive document viewer

Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2

Sánchez Mora, Patricia

Abstract

Este Trabajo de Fin de Grado presenta el diseño e implementación de un sintetizador MIDI utilizando el chip de síntesis FM Yamaha YM3812 (también conocido como OPL2). El objetivo principal del proyecto es desarrollar un sistema capaz de interpretar mensajes MIDI estándar y generar sonido en tiempo real mediante síntesis de modulación por frecuencia (FM). Para ello, se ha utilizado un microcontrolador Arduino Uno como unidad principal de procesamiento, que recibe las señales MIDI, las interpreta y comunica los parámetros necesarios al chip OPL2. El proyecto abarca tanto el diseño hardware como el desarrollo del firmware en C/C++ para gestionar la comunicación, el mapeo de notas, la asignación de canales y la gestión de patches. El sintetizador permite la ejecución de múltiples notas simultáneas y la utilización de distintos timbres musicales, aprovechando la capacidad del chip para la síntesis polifónica y la manipulación de parámetros como el ataque, la caída y la modulación. El resultado es un dispositivo funcional, de bajo coste y alto valor didáctico, que demuestra la viabilidad de reutilizar tecnologías clásicas de síntesis sonora en un contexto educativo y experimental.

Full text

Equation Chapter 1 Section 1 Trabajo Fin de Grado en Ingeniería de las Tecnologías de Telecomunicación Dpto. Ingeniería Electrónica Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2025 Autora: Patricia Sánchez Mora Tutor: Manuel Ángel Perales Esteve Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 iii Trabajo Fin de Grado en Ingeniería de las Tecnologías de Telecomunicación Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 Autora: Patricia Sánchez Mora Tutor: Manuel Ángel Perales Esteve Profesor titular Dpto. de Ingeniería Electrónica Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2025 v Trabajo Fin de Grado: Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 Autora: Patricia Sánchez Mora Tutor: Manuel Ángel Perales Esteve El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2025 El Secretario del Tribunal vii Agradecimientos En primer lugar, gracias a mi tutor Manuel Ángel Perales por su orientación y compromiso durante todo el proceso. Su experiencia y sus aportaciones han sido fundamentales para superar cada etapa del proyecto. Gracias por toda la atención recibida, tiempo invertido y hacer que este proyecto haya sido una experiencia altamente enriquecedora. Agradezco especialmente a mi padre, Juan Carlos, por su apoyo incondicional y su infinita paciencia; por acompañarme con sus consejos, su cariño y su confianza desde el principio hasta el final. Gracias también a mi familia, a mi madre Loli y a mi hermano Juan Carlos, por su constante comprensión, su ánimo y su confianza en mí a lo largo de esta etapa. Un agradecimiento también a mi abuela Juani, por el cariño y la cercanía que me ha brindado siempre. Y a mi abuelo Carlos, con quien me hubiera gustado compartir el final de este camino y cuyo recuerdo ha estado presente en todo momento. A todos, gracias de corazón. ix Resumen Este Trabajo de Fin de Grado presenta el diseño e implementación de un sintetizador MIDI utilizando el chip de síntesis FM Yamaha YM3812 (también conocido como OPL2). El objetivo principal del proyecto es desarrollar un sistema capaz de interpretar mensajes MIDI estándar y generar sonido en tiempo real mediante síntesis de modulación por frecuencia (FM). Para ello, se ha utilizado un microcontrolador Arduino Uno como unidad principal de procesamiento, que recibe las señales MIDI, las interpreta y comunica los parámetros necesarios al chip OPL2. El proyecto abarca tanto el diseño hardware como el desarrollo del firmware en C/C++ para gestionar la comunicación, el mapeo de notas, la asignación de canales y la gestión de patches El sintetizador permite la ejecución de múltiples notas simultáneas y la utilización de distintos timbres musicales, aprovechando la capacidad del chip para la síntesis polifónica y la manipulación de parámetros como el ataque, la caída y la modulación. El resultado es un dispositivo funcional, de bajo coste y alto valor didáctico, que demuestra la viabilidad de reutilizar tecnologías clásicas de síntesis sonora en un contexto educativo y experimental. Figura 2-22. Estructura del registro KSL/Total Level 20 Figura 2-23. Estructura del registro Attack/Decay 21 Figura 2-24. Estructura del registro Sustain Level / Release Rate 21 Figura 2-25. Estructura del registro Block/FNumber 21 Figura 2-26. Ejemplo de acceso a registros 22 Figura 2-27. Interfaz a CPU 23 Figura 3-1. Distribución de pines de Arduino UNO 26 Figura 3-2. Distribución de pines del registro de desplazamiento 74HC595 27 Figura 3-3. Distribución de pines del YM3812 27 Figura 3-4. Distribución de pines del DAC Y3014B 28 Figura 3-5. Distribución de pines del OP-AMP MCP6242-E/P 28 Figura 3-6. Distribución de pines del optoacoplador 6N139 29 Figura 3-7. Esquema eléctrico del circuito de entrada MIDI con optoacoplador 30 Figura 3-8. Esquema de conexión de pines del Arduino Uno al bus de control y datos del OPL2 30 Figura 3-9. Esquema de conexión del módulo OPL2 32 Figura 3-10. Diseño PCB del circuito completo. a) Capa superior b) Capa inferior 33 Figura 3-11. Resultado final PCB del circuito completo. Capa superior. 33 Figura 3-12. Resultado final PCB del circuito completo. Capa inferior. 34 Figura 4-1. Esquema de conexión de la primera versión del módulo OPL2 35 Figura 4-2. Diseño PCB de la primera versión del módulo OPL2 36 Figura 4-3. Estructura de ficheros 37 Figura 4-4. Estructura del código 37 Figura 4-5. Sintetizador MAPE-SYNTH 43 Figura 4-6. Estructura final de los ficheros 48 1 1 INTRODUCCIÓN ransformar ideas en realidades palpables es algo que muchos podrían calificar como “magia”. ¿Cómo es posible que aquello que solo habita en nuestra imaginación llegue a convertirse en algo físico? ¿Cómo es posible capturar y reproducir emociones tan abstractas como las que nos provoca una melodía? La respuesta no está en la magia, sino en la ciencia, las matemáticas y la ingeniería. La ingeniería actúa como un puente entre el pensamiento abstracto y el mundo tangible; es, en esencia, la disciplina que permite traducir la imaginación humana en soluciones concretas capaces de transformar nuestro entorno. La música encarna ese componente mágico. Es una forma de expresión profundamente emocional, capaz de evocar recuerdos, estados de ánimo o hacer viajar con solo unos acordes. Y, sin embargo, también es una manifestación física, compuesta por vibraciones, frecuencias, patrones y estructuras. Resulta fascinante pensar que podemos escuchar música en cualquier momento sin tener una orquesta presente, que podemos almacenar y reproducir sonidos complejos con dispositivos diminutos, que somos capaces de imitar o incluso reinventar instrumentos con herramientas electrónicas. ¿Cómo es posible que algo tan intangible como una emoción pueda representarse con tanta precisión mediante circuitos, código y ondas? Figura 1-1. Música en directo VS Música digitalizada La respuesta es simple: física. El sonido no es más que una onda mecánica que se propaga a través de un medio, generalmente el aire. Tiene propiedades físicas medibles: frecuencia, amplitud, timbre, envolvente… que T Any sufficiently advanced technology is indistinguishable from magic. - Arthur C. Clarke - Introducción 2 determinan cómo lo percibimos. Cada instrumento musical genera ondas con formas y características particulares: un piano produce un ataque brusco seguido de una caída armónica; un violín puede mantener un tono sostenido modulando su vibrato. Estas diferencias no son mágicas, son el resultado de patrones específicos de vibración que pueden describirse, representarse y, por tanto, reproducirse electrónicamente. Sabiendo que el sonido es, en esencia, una combinación de impulsos y frecuencias, es posible descomponerlo, analizarlo, e incluso sintetizarlo desde cero. Esta es la base sobre la que se construye la síntesis sonora: si podemos entender las propiedades que hacen único un timbre, también podemos replicarlas o modificarlas para dar lugar a sonidos completamente nuevos. Figura 1-2. Formas de onda para distintos instrumentos reproduciendo la misma nota A partir de este conocimiento, surgen los sintetizadores: dispositivos diseñados para manipular intencionadamente estos parámetros del sonido y generar nuevas formas de expresión musical. En lugar de depender de cuerdas, lengüetas o tubos para producir vibraciones, los sintetizadores emplean circuitos electrónicos, algoritmos y osciladores para construir el sonido desde su base. Aprovechan el hecho de que todo timbre, por complejo que sea, puede descomponerse en ondas simples y parámetros modificables. Así, permiten no solo imitar con gran fidelidad instrumentos acústicos tradicionales, sino también crear texturas sonoras imposibles de producir por medios físicos convencionales. Su aparición en la segunda mitad del siglo XX marcó el inicio de una nueva era musical, dando lugar a una expansión creativa sin precedentes. Desde los primeros sintetizadores analógicos hasta los complejos sistemas digitales actuales, esta tecnología ha influido profundamente en géneros como el synth-pop, el ambient, el hip hop, la música electrónica o incluso el rock progresivo. Además de su impacto artístico, los sintetizadores han desempeñado un papel central en la producción musical moderna, integrándose tanto en estudios de grabación profesionales como en actuaciones en directo e incluso dispositivos portátiles. Más allá de su dimensión artística y musical, el sintetizador constituye un objeto de estudio fascinante desde el punto de vista técnico. Su diseño y funcionamiento involucran múltiples campos del conocimiento, desde la electrónica analógica y digital, el procesamiento de señales, hasta la programación de sistemas embebidos y el manejo de protocolos de comunicación digital. La síntesis sonora, en cualquiera de sus formas, es una aplicación multidisciplinar que ejemplifica cómo la ingeniería puede transformar conceptos abstractos y matemáticos en experiencias sensoriales tangibles. Entre los distintos enfoques de síntesis desarrollados a lo largo del tiempo, la síntesis FM (modulación de frecuencia) destacó especialmente por su eficiencia y riqueza tímbrica. Este método, popularizado en los años 80 por Yamaha, permitió obtener sonidos complejos y expresivos con un consumo relativamente bajo de recursos computacionales, lo que facilitó su integración en teclados, consolas de videojuegos y ordenadores personales. Uno de los chips más emblemáticos de esta época fue el YM3812, más conocido como OPL2, un generador de sonido que se convirtió en un clásico en la música de videojuegos y software de entretenimiento de la época. 3 3 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 El OPL2 supuso un punto de inflexión en la accesibilidad de la síntesis digital. Capaz de generar hasta nueve voces simultáneas mediante síntesis FM de dos operadores, este chip ofrecía un equilibrio notable entre versatilidad, compacidad y carácter sonoro distintivo. Su sonido, reconocible y nostálgico, aún hoy despierta interés entre músicos, diseñadores sonoros y aficionados a la retroinformática. Gracias a su diseño y a su control mediante registros accesibles vía microcontrolador, el OPL2 sigue siendo una plataforma ideal para explorar la síntesis FM desde un enfoque técnico. Figura 1-3. Chip Yamaha YM3812 OPL2 El impacto del OPL2 no se limitó únicamente a sus capacidades técnicas, sino que se materializó en productos concretos que definieron una era. Este chip fue integrado en tarjetas de sonido icónicas como la AdLib y la primera Sound Blaster, componentes fundamentales en la evolución del audio en computadoras personales durante las décadas de 1980 y 1990. Gracias a él, los ordenadores adquirieron por primera vez una capacidad sonora expresiva y compleja, que transformó la experiencia de usuario en entornos multimedia y videojuegos. La síntesis FM del OPL2 no solo permitió una recreación eficaz de instrumentos reales, sino que introdujo una paleta tímbrica completamente nueva, que definió el carácter sonoro de toda una generación. Figura 1-4. Tarjetas de sonido de los 80 y 90 que utilizan el OPL2 A pesar de las limitaciones que presenta frente a las tecnologías de síntesis digital actuales, el chip Yamaha OPL2 sigue despertando un notable interés por su timbre distintivo, su eficiencia de diseño y su capacidad para ofrecer resultados expresivos con recursos relativamente modestos. Elegir el OPL2 para este proyecto es una oportunidad para explorar su potencial desde una perspectiva actual, reinterpretando sus posibilidades a través de herramientas actuales y aplicando principios de diseño electrónico y programación moderna. Introducción 4 En este sentido, el presente trabajo toma como referencia el proyecto publicado en el blog Things Made Simple, una plataforma dedicada a divulgar proyectos de electrónica con un enfoque accesible, formativo y técnico. En dicho blog, su autor comparte el diseño de un sintetizador MIDI basado en el chip Yamaha OPL2, demostrando cómo, mediante conocimientos sólidos y creatividad, es posible dar forma a ideas aparentemente complejas y construir sistemas funcionales desde cero. El sistema desarrollado en este proyecto se basa en esa propuesta, tanto a nivel de esquemático como de código, ya desarrollado por el autor. A partir de esa base, se ha llevado a cabo un análisis en profundidad del funcionamiento del chip OPL2, la adaptación del diseño a los medios disponibles, y la elaboración de una documentación detallada del proceso de implementación. El objetivo ha sido comprender y reproducir el funcionamiento del sintetizador, abordando tanto el diseño de la circuitería como la adaptación de la programación necesaria para recibir mensajes MIDI estándar y generar sonido utilizando la síntesis FM del OPL2. Para ello, se ha utilizado como microcontrolador principal un Arduino Uno, plataforma ampliamente utilizada por su facilidad de uso, entorno de desarrollo intuitivo y amplia comunidad de soporte. En esta implementación, el Arduino actúa como intermediario entre el controlador MIDI y el chip OPL2: recibe los datos MIDI, los interpreta y genera las instrucciones necesarias para la producción de sonido. Aunque el diseño original de Things Made Simple ha sido una base fundamental, el trabajo realizado en este proyecto ha consistido en la comprensión del sistema, la documentación técnica del proceso, la adaptación del montaje y código a este microprocesador en concreto, ya que el diseño original contaba con un AVR128DA28. Figura 1-5. Arduino Uno El desarrollo del dispositivo implicará además de la adaptación de las rutinas necesarias para el manejo del chip, el diseño y montaje de un circuito impreso (PCB) que permita integrar todos los elementos de forma compacta y funcional. Este PCB incluirá el OPL2, el Arduino, una entrada MIDI estándar (DIN de 5 pines), sistemas de alimentación, amplificación de salida y componentes auxiliares. Dentro de este diseño, se incluye la interfaz de entrada MIDI, que incorpora un optoacoplador para garantizar el aislamiento eléctrico entre el dispositivo externo y el microcontrolador. Esta interfaz asegura la integridad de la señal MIDI, protegiendo el sistema frente a posibles interferencias o daños, y es el puente que permite que el Arduino reciba correctamente los mensajes estándar MIDI. A continuación, se muestra el esquema general del sistema, donde se pueden observar los principales módulos y su interconexión: Figura 1-6. Esquema general del sintetizador MIDI basado en Yamaha OPL2 5 5 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 Este enfoque permite abordar el proyecto desde múltiples áreas de la ingeniería: la electrónica digital y analógica, el diseño de circuitos, la programación de microcontroladores, y el estudio de protocolos de comunicación musical, como el MIDI. Todo ello está orientado a un objetivo concreto: la creación de un dispositivo musical que combine el potencial de un chip histórico con la accesibilidad del hardware actual, sirviendo tanto como herramienta creativa como experiencia formativa en el ámbito de la ingeniería. A lo largo de este trabajo se desarrollarán de forma detallada todas las etapas necesarias para la construcción del sintetizador MIDI basado en el Yamaha OPL2. En primer lugar, se realizará un repaso teórico sobre la síntesis FM, el estándar MIDI y las características técnicas del chip YM3812, contextualizando su funcionamiento interno. A continuación, se abordará el diseño del sistema electrónico, incluyendo la elección de componentes, la elaboración del esquema eléctrico y el diseño del circuito impreso. Posteriormente, se presentará el desarrollo del firmware necesario para la gestión del protocolo MIDI y el control del OPL2 mediante el microcontrolador Arduino Uno. Finalmente, se documentarán las conclusiones obtenidas y posibles líneas de mejora o ampliación. Este enfoque permitirá comprender no solo el funcionamiento del sintetizador resultante, sino también las distintas disciplinas de ingeniería implicadas en su desarrollo. 7 2 MARCO TEÓRICO ntes de abordar el desarrollo práctico del proyecto, es necesario establecer una base teórica que permita comprender los principios y tecnologías implicadas. Este apartado reúne los conceptos fundamentales relacionados con la síntesis sonora, el protocolo MIDI y el funcionamiento del chip Yamaha OPL2. 2.1. Sintetizadores 2.1.1 Principios básicos del sonido y síntesis sonora El sonido, en su forma más esencial, es una vibración que se propaga como una onda mecánica a través de un medio elástico, como el aire. Desde el punto de vista físico, toda onda sonora puede caracterizarse por una serie de parámetros fundamentales: la frecuencia, que determina la altura del sonido; la amplitud, que se asocia a su intensidad o volumen percibido; la fase, que describe el desplazamiento temporal de la onda; y la forma de onda, que afecta directamente al timbre o color del sonido. Figura 2-1. Onda de sonido Cada instrumento musical genera una forma de onda característica, producto de su estructura física y su modo de excitación (cuerda frotada, aire vibrando, membrana percutida, etc.). Esta forma de onda no es una onda pura, sino una combinación de múltiples componentes sinusoidales que pueden estar relacionadas armónicamente o no. Esta idea, derivada de la teoría de Fourier, permite entender el timbre como el resultado de una distribución específica de frecuencias y amplitudes, que se puede representar y analizar en el dominio frecuencial. A Marco Teórico 8 Además del contenido espectral, el sonido también posee una evolución temporal, comúnmente representada por su envolvente, es decir, el cambio de amplitud a lo largo del tiempo. Una de las formas más extendidas de modelar esta envolvente es mediante el esquema ADSR (Attack, Decay, Sustain, Release), que describe cómo se comporta un sonido desde su inicio hasta su desaparición. • Ataque (Attack): es la fase que contiene el transitorio inicial, normalmente caracterizada por una gran variedad espectral. • Decaimiento (Decay): es la ley de amplitud decreciente del sonido al final del transitorio inicial. • Sostenido (Sustain): es la fase central del sonido. El sonido tiene una amplitud y un contenido de frecuencia casi constantes. • Liberación (Release): al final de la excitación externa, el sonido disminuye su intensidad más o menos rápidamente hasta anularse por completo. Figura 2-2. Envolvente ADSR Figura 2-3. Comportamiento típico de la envolvente de ciertos instrumentos: a) órgano b) viento-madera (clarinete, saxofón, oboe, etc.) c) viento-metal (trompeta, trombón, etc) d) piano, vibráfono, guitarra, etc. Con el desarrollo de la síntesis sonora, se ha logrado no solo analizar estos parámetros, sino también reproducirlos y modificarlos de manera controlada. La síntesis es el proceso mediante el cual se generan sonidos artificiales manipulando sus propiedades fundamentales. Dependiendo del enfoque, existen diversos tipos de síntesis, entre ellos: • Síntesis aditiva. El sonido se construye como una superposición de sonidos elementales, generalmente señales sinusoidales moduladas en amplitud, frecuencia y/o fase. (Risset 1965). 9 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 Figura 2-4. Esquema del modelo de síntesis aditiva • Síntesis sustractiva. Parte de una señal rica en contenido armónico, como ruido o una onda compleja, y elimina frecuencias no deseadas mediante filtrado digital (Rabiner y Gold, 1975). Este filtrado es normalmente un filtro de paso bajo con una pendiente de 12-24 dB/oct. Figura 2-5. Esquema del modelo de síntesis sustractiva • Síntesis global (o no lineal). El ejemplo más conocido de este tipo de métodos es el audio FM. Esta técnica actualizada por Chowning (1973) revolucionó los sintetizadores comerciales. Las ventajas de este método son que requiere muy pocos parámetros y que un pequeño número de operaciones numéricas puede generar espectros complejos. Sin embargo, no se adaptan a un control preciso de la señal, ya que ligeras modificaciones de los parámetros inducen transformaciones radicales de la señal. También se han desarrollado otros métodos afines, como las técnicas de conformación de ondas (Arfib 1979; Le Brun 1979). Figura 2-6. Esquema del modelo de síntesis FM Marco Teórico 16 Figura 2-15. a) Partitura b) Mensajes MIDI correspondientes. 2.3 Yamaha YM3812 (OPL2) El OPL2 (Yamaha YM3812) es un chip sintetizador de sonido FM (modulación de frecuencia) desarrollado por Yamaha y ampliamente utilizado en hardware de audio de computadoras durante los años 80 y principios de los 90. El sistema de modulación de frecuencia permite la producción de un gran abanico de sonidos usando control de software. El OPL2 tiene un oscilador de baja frecuencia integrado para crear vibrato y efectos AM, reduciendo la cantidad de programación requerida para efectos especiales. 2.3.1 Arquitectura interna El OPL2 posee una arquitectura basada en canales de sonido, donde cada canal representa una voz musical independiente. En total, el chip puede generar hasta 9 canales simultáneos. Cada uno de ellos está compuesto por dos operadores: un modulador y un portador. 17 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 • Operador: es el bloque básico de construcción de síntesis FM. Está compuesto por un oscilador que genera la onda de sonido y por una envolvente que ajusta el nivel a lo largo del tiempo. Cada canal del YM3812 incluye dos de estos operadores y se combinan usando dos posibles síntesis: síntesis aditiva o síntesis de frecuencia. En este caso, se hará uso de la segunda. • Modulador: El primer operador altera la frecuencia del portador, creando armónicos y timbres complejos característicos de la síntesis FM. • Portador: El otro operador produce la señal final de audio, ya sea modulada o no, que será enviada a la salida del chip. Figura 2-16. Algoritmos de operadores Para manipular cada uno de los parámetros que determinan el comportamiento de los operadores, el OPL2 dispone de un conjunto de registros internos que se programan a través de una interfaz digital. Esta interfaz está compuesta por una serie de pines de entrada y salida, dispuestos en un layout específico. A través de estos pines se transmite la información de control (como qué registro se desea modificar, y con qué valor), así como el reloj del sistema y las señales de control necesarias para su funcionamiento. Figura 2-17. Distribución de pines Marco Teórico 18 La función de cada pin es la siguiente: • øM: Reloj maestro • øSY – SH: Se necesita un reloj para convertir la salida digital del OPL2 en señales analógicas (øSY) y señales de sincronización (SH). Estas señales permiten conexión directa con un convertidor digital analógico. • D0 ~ D7: Bus de datos bidireccional de 8 bits • 𝐂𝐒 @ @ @ @ – 𝐑𝐃 @ @ @ @ – 𝐖𝐑 @ @ @ @ @ – A0: Bus de control de D0 ~ D7 Tabla 2–1. Modos de estado del bus de control CS @ @ @ RD @ @ @ @ WR @ @ @ @ @ A0 0 1 0 0 Modo de escritura de dirección de registro 0 1 0 1 Modo de escritura de contenidos del registro 0 0 1 0 Modo de lectura de estado del OPL2 0 0 1 1 Datos del bus D0 ~ D7 no asegurados. Control de datos no es posible. 1 x x x Bus de datos en alta impedancia. Modo inactivo. • 𝐈𝐑𝐐 @ @ @ @ @ : Señal de interrupción enviada desde cualquiera de los temporizadores. Cuando el tiempo programado por un temporizador se completa, se pone a 0. Las interrupciones pueden ser enmascaradas por un programa. • 𝐈𝐂 @ @ @ : Vacía los contenidos de todos los registros e inicializa el OPL2. • MO: Señal de música o audio de salida convertida a una señal de 13 bits. Se necesita un convertidor D/A para convertir esta señal digital en su equivalente analógico. • GND: Pin de tierra. • Vcc: +5V de alimentación. Figura 2-18. Diagrama de bloques del OPL2 19 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 2.3.2 Registros El OPL2 se controla completamente mediante registros programables. Estos registros permiten configurar los parámetros de cada canal y operador, como la forma de la envolvente para una melodía, el factor de modulación, la frecuencia… Estos datos pueden ser combinados para generar el sonido de un piano, violín u otro instrumento. Los registros son un área de 777 bits a los cuales se puede acceder mediante direcciones. Por tanto, los datos se pueden almacenar en el OPL2 mediante, primero, el envío de la dirección que va a almacenar estos datos, y después, el envío de dichos datos. Figura 2-19. Mapa de direcciones OPL2 Algunos de estos registros son: • Temporizadores: ($02, $03, $04) El OPL2 cuenta con tres temporizadores. El temporizador 1 tiene una resolución de 80 µs, mientras que el temporizador 2 tiene una resolución de 320 µs. El temporizador 3 se encarga de parar, empezar o levantar banderas de control para los otros dos temporizadores. Permiten sincronizar efectos musicales, automatización de eventos o incluso alertas de temporización en una secuencia de audio. • AM/VIB/EG-TYP/KSR/Multiple ($20 – 35) Este registro controla las características del operador, es decir, de cada generador de onda individual. Cada dirección corresponde a un operador. Marco Teórico 20 Figura 2-20. Estructura del registro AM/VIB/EG-TYP/KSR/Multiple A su vez, cada campo define un parámetro. Se dividen en los siguientes: o D0-D3 (Multiplicador de frecuencia): Controlan las frecuencias de las ondas portadora y moduladora. Modifica el timbre. o D4 (KSR): Controla la velocidad de respuesta a la nota tocada. Simula el efecto real de un instrumento en el que las etapas de ataque y decaída aumentan cuando el intervalo es mayor. o D5 (EG-TYP): Controla el tipo de envolvente, establece el sonido en modo decreciente o continuo cuando desaparece. La diferencia está en el nivel de liberación. Figura 2-21. Modo decreciente o continuo o D6 (VIB): Controla el vibrato con frecuencia de 6,4 Hz. o D7 (VIB): Controla la modulación de amplitud (trémolo) con frecuencia de 3,7 Hz. • KSL/Total Level ($40 – 55) Figura 2-22. Estructura del registro KSL/Total Level o D0 – D5 (Total Level): Define el volumen base del operador. o D6 – D7 (KSL): Controla la atenuación según la altura de la nota. Simula la naturaleza de los instrumentos donde las notas agudas suenan más suaves. 21 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 • Attack/Decay Rate ($60 – 75) Figura 2-23. Estructura del registro Attack/Decay o D0 – D3 (Attack Rate): Define la velocidad con la que el sonido llega a su máximo. o D4 – D7 (Decay Rate): Define la velocidad con la que el sonido cae tras el ataque. • Sustain Level / Release Rate ($80 – 95) Figura 2-24. Estructura del registro Sustain Level / Release Rate o D0 – D3 (Release Rate): Nivel de volumen en la fase de sostenido. o D4 – D7 (Sustain Level): Define la velocidad con la que el sonido desaparece al soltar la nota. • Block/FNumber ($A0 B8) Figura 2-25. Estructura del registro Block/FNumber Marco Teórico 22 o D0 – D7 ($A*), D0 – D1 ($B*) (F-Number): Controla la frecuencia base de la nota. o D2 – D4 (Block): Selecciona la octava (rango de frecuencia). o D5 (Key-ON): Dispara la reproducción de la nota. • Feedback /Connection ($C0 – C8) Define el carácter del sonido. Controla el factor de modulación para la retroalimentación y el modo de conexión entre operadores: FM o aditiva. Si, por ejemplo, el contenido del registro con la dirección 0x41 fuese 0x4C: Figura 2-26. Ejemplo de acceso a registros Sabemos que la dirección 0x41 corresponde con el registro KLS (Key Level Scaling) / Total Level. Los dos bits más significativos se corresponden con KSL, nivel de atenuación según la altura de la nota, y el resto de los bits se corresponden con Total Level, el volumen base del operador. 2.3.3 Creación de música La síntesis de sonido por modulación de frecuencia (FM) se basa en utilizar un operador modulador que altera la señal de un operador portador, generando así complejas formas de onda y timbres ricos. De esta manera, el pitch, el timbre y el volumen del sonido pueden definirse a través de una manipulación precisa de los parámetros fundamentales de la síntesis FM: el nivel de salida del portador, el nivel de salida del modulador, el grado de retroalimentación del modulador, así como las frecuencias del portador y del modulador. 23 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 Tabla 2–2. Relación entre parámetros FM y parámetros OPL2 Parámetro FM Parámetros aplicables MIN (cambio en el sonido) MAX Nivel de salida del portador Total Level (Datos para A, D, S y R) Nivel mínimo Nivel máximo Nivel de salida del modulador Sonido suave, cálido Sonido brillante Grado de retroalimentación del modulador FB Tono normal Tono estridente Frecuencia del portador MULTI (Block / F-Number) Pitch bajo Pitch alto Frecuencia del modulador Armónicos cercanos Armónicos lejanos El OPL2 permite que toda esta información sea transmitida a un microprocesador a través del bus de datos y así crear un sistema de generación de sonido FM. Figura 2-27. Interfaz a CPU 25 3 DISEÑO DEL PROYECTO n este capítulo se describe el diseño técnico del sistema desarrollado, abarcando tanto sus requisitos funcionales como los elementos físicos y lógicos que lo componen. El objetivo es ofrecer una visión clara y detallada de la estructura general del sistema, mostrando los componentes elegidos y cómo se integran para cumplir con los objetivos del proyecto. 3.1 Requisitos del sistema 3.1.1 Requisitos funcionales Para este proyecto, cuyo objetivo es generar sonido mediante síntesis FM controlada por señales MIDI, se han identificado los siguientes requisitos: • Recepción de mensajes MIDI: El sistema debe ser capaz de interpretar mensajes MIDI estándar, incluyendo Note On, Note Off, Program Change, entre otros. • Generación de sonido mediante síntesis FM: Utilizando el chip OPL2, el sistema debe sintetizar sonidos musicales a partir de los mensajes MIDI recibidos. • Reproducción polifónica: La arquitectura debe ser capaz de generar múltiples notas simultáneas, dentro de las capacidades del OPL2. • Selección de instrumentos: El usuario debe poder seleccionar distintos sonidos/timbres (“patches”) disponibles mediante comandos MIDI. 3.1.2 Requisitos no funcionales Además de los requisitos anteriores, el sistema también debe cumplir con los siguientes: • Compatibilidad MIDI: El sistema debe respetar la especificación estándar MIDI 1.0, garantizando compatibilidad con teclados, secuenciadores y DAWs externos. • Bajo consumo energético: Dado que puede usarse en dispositivos portátiles o embebidos, el sistema debe optimizar el uso de energía. • Tiempo de respuesta: La latencia entre la recepción del mensaje MIDI y la generación del sonido debe ser mínima para asegurar una respuesta adecuada en tiempo real. • Estabilidad y robustez: El sistema debe funcionar de forma estable durante sesiones prolongadas sin errores o bloqueos. • Modularidad y mantenibilidad: El diseño debe permitir futuras ampliaciones, como modos o controles adicionales o visualización de datos MIDI. E Diseño del Proyecto 32 Figura 3-9. Esquema de conexión del módulo OPL2 33 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 3.3.4 Diseño del circuito en PCB Con el objetivo de obtener un resultado más compacto, robusto y profesional, se ha optado por integrar toda la circuitería en una única placa de circuito impreso (PCB). a) b) Figura 3-10. Diseño PCB del circuito completo. a) Capa superior b) Capa inferior Figura 3-11. Resultado final PCB del circuito completo. Capa superior. Diseño del Proyecto 34 Figura 3-12. Resultado final PCB del circuito completo. Capa inferior. 35 4 DESARROLLO DEL PROTOTIPO ste capítulo detalla el proceso de ensamblaje y programación del sistema diseñado. En él se documentan los pasos seguidos en el montaje físico de los componentes, así como la elaboración del firmware que permite el control del chip OPL2 desde el controlador. 4.1 Montaje del módulo OPL2 Como primer paso, se creó una primera placa de circuito impreso más pequeña que la mostrada en el capítulo anterior. En ésta, se recogía una primera versión del circuito, sin contar todavía con la entrada MIDI. Figura 4-1. Esquema de conexión de la primera versión del módulo OPL2 E Desarrollo del Prototipo 36 Figura 4-2. Diseño PCB de la primera versión del módulo OPL2 Esta primera etapa consistió en el taladrado y soldadura de los distintos componentes en la tarjeta de circuito impreso. Las salidas IC, A0, WR, CS, MOSI, LATCH y SCK, además de la alimentación de 5V y la toma a tierra, fueron conectadas a los pines correspondientes en el Arduino mediante cableado. A continuación, se fueron implementando distintos bloques de código software desde el Arduino, probando y añadiendo así distintas funcionalidades para que el OPL2 las realizara. 4.2 Programación y firmware A nivel firmware, el proceso se dividió en cuatro etapas, implementando distintas funcionalidades en cada una. 4.2.1 Producción de sonido En esta primera etapa, el objetivo era reproducir notas de la forma más básica posible. En concreto, se pretendía reproducir el acorde de Fa mayor 7ª. Para tocar una nota en este chip, necesitamos, además de que el hardware funcione, una biblioteca que interactúe con el chip, una voz correctamente configurada y la capacidad de activar/desactivar una nota en la frecuencia adecuada. Para esto, el código se distribuye en tres archivos: • YM3812_Breadboard.ino donde se encuentra el programa principal. Aquí se encuentran las funciones setup y loop. • YM3812.h es el fichero de cabecera que define la biblioteca YM3812 que envuelve las funciones del chip en una clase. • YM3812.cpp proporciona las implementaciones de las funciones definidas en el fichero de cabecera. 37 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 Figura 4-3. Estructura de ficheros Dentro de la clase YM3812, hay dos tipos diferentes de funciones: las funciones de control de chip y las de registro. Las funciones de control de chip ordenan al microcontrolador que envíe datos al YM3812 (o lo reinicie). Su implementación depende totalmente del tipo de microcontrolador utilizado y de cómo estén configurados sus pines. Las funciones de registro utilizan estas funciones de control de chip para manipular los ajustes de registro en el YM3812. Figura 4-4. Estructura del código 4.2.1.1 Funciones de control del chip Tenemos dos funciones de este tipo: sendData y reset. 4.2.1.1.1 sendData, Esta función envía los datos al YM3812. void YM3812::sendData( uint8_t reg, uint8_t val ){ PORTD |= DATA_LED; PORTD &= ~YM_CS; // Habilita el chip Desarrollo del Prototipo 38 PORTD &= ~YM_A0; // Pone el chip en modo de selección de registro SPI.transfer(reg); // Pone la ubicación del registro en el bus de datos a través del puerto SPI PORTD |= YM_LATCH; // Enclava la ubicación del registro en el 74HC595 delayMicroseconds(1); // Espera un tic PORTD &= ~YM_LATCH; // Pone el latch a nivel bajo PORTD &= ~YM_WR; // Pone la escritura a nivel bajo para comenzar el ciclo de escritura delayMicroseconds(10); // Retraso para que el chip complete el ciclo de escritura PORTD |= YM_WR; // Pone la escritura a nivel alto delayMicroseconds(10); // Retraso hasta que el chip esté listo para continuar PORTD |= YM_A0; // Pone el chip en modo de escritura de datos SPI.transfer(val); // Pone el valor en el bus de datos a través del puerto SPI PORTD |= YM_LATCH; // Engancha el valor en el 74HC595 delayMicroseconds(1); // Espere un tic PORTD &= ~YM_LATCH; // Baja latch PORTD &= ~YM_WR; // Pone la escritura a nivel bajo delayMicroseconds(10); // Retraso para que el chip complete el ciclo de escritura PORTD |= YM_WR; // Poner la escritura a nivel alto delayMicroseconds(10); PORTD |= YM_CS; // Pone el modo de selección a nivel alto para inhabilitar el chip PORTD &= ~DATA_LED; } 4.2.1.1.2 reset, La función reset realiza cuatro tareas principales. Primero inicializa el bus SPI, luego reinicia el YM3812 poniendo su línea de reinicio a nivel bajo y luego a nivel alto. Los retardos aseguran que el chip está listo antes de continuar. En tercer lugar, pone a cero las variables de caché de registro almacenadas en la clase (para que coincidan con lo que debería haber en el chip ahora). Y finalmente, establece la bandera Waveset para permitir todos los tipos de forma de onda. void YM3812::reset(){ SPI.begin(); //Inicializa el bus SPI PORTD &= ~YM_IC; delay(10); // Pone la línea de inicialización a nivel bajo y luego lo a nivel alto PORTD |= YM_IC; delay(10); reg_01 = reg_08 = reg_BD = 0; // Limpia los ajustes globales for( uint8_t ch = 0; ch < YM3812_NUM_CHANNELS; ch++ ){ // Bucle por todos los canales para poner todos los registros a 0 reg_A0[ch] = reg_B0[ch] = reg_C0[ch] = 0; } 39 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 for( uint8_t op = 0; op < YM3812_NUM_OPERATORS; op++ ){ // Bucle por todos los operadores para poner todos los registros a 0 reg_20[op] = reg_40[op] = reg_60[op] = reg_80[op] = reg_E0[op] = 0; } regWaveset( 1 ); } // Habilita todas las formas de onda 4.2.1.2 Funciones de Registro Las funciones de registro se basan en las funciones de control del chip y siguen un patrón muy similar: 1. Enmascara el nuevo valor para asegurar que tiene el número correcto de bits para la propiedad que se quiere cambiar 2. Busca el valor actual del registro que contiene la propiedad 3. Borra los bits asociados con la propiedad que quiere sobrescribir 4. Inserta los bits del valor de la propiedad enmascarada en el registro 5. Guarda el nuevo valor combinado del registro en la caché del registro y lo envía al YM3812 Para esto, creamos una macro que una vez escrita, nos permitirá guardar los detalles asociados a la manipulación de bits para no tener que preocuparnos por esto. #define SET_BITS( regVal, mask, offset, newVal) ((regVal) = ((regVal) & (~((mask)<<(offset)))) | (((newVal) & (mask)) << (offset))) Esta macro toma un valor de registro (regVal) y combina en él un nuevo valor de propiedad (newVal). La máscara de bits determina qué bits de newVal incluir, y offset determina dónde insertar esos bits dentro de regVal. Vamos a querer modificar tres tipos de registros: globales, de canal y de operador. Para ello, vamos a acceder las direcciones de cada registro de acuerdo con el parámetro que queremos modificar, como se ha visto anteriormente. 4.2.1.2.1 Funciones,de,registros,globales, Los registros globales sólo tienen una instancia de su valor, por lo que pueden representarse con enteros. Los de canales y operadores tienen múltiples instancias de sus registros por lo que usamos arrays de enteros para almacenarlos. Por ejemplo, si quisiéramos establecer la profundidad del trémolo: void regTremoloDepth( uint8_t val ){ sendData(0xBD, SET_BITS( reg_BD, 0b00000001, 7, val ) ); } regVal ha tomado el valor del registro que queremos modificar y como solo estamos actualizando un bit, cambiamos bitMask por la constante 0b00000001. Luego, debido a que la bandera DeepTrem se encuentra en el bit 7, cambiamos offset por la constante, 7. Por último, cambiamos newVal por la variable que se pasó a la función. Desarrollo del Prototipo 40 4.2.1.2.2 Funciones,de,registros,de,canal, Para actualizar los registros de canal se sigue el mismo procedimiento, con la diferencia de que ahora existen 9 registros por cada dirección (uno por cada canal). En vez de enteros, se usan arrays. También, hay que elegir el valor correcto basándonos en el canal que queremos actualizar. Por ejemplo, para modificar la retroalimentación de un canal específico: void regChFeedback( uint8_t ch, uint8_t val ){ sendData(0xC0+ch, SET_BITS( reg_C0[ch], 0b00000111, 1, val ) ); } A continuación, tenemos que calcular la dirección del registro que queremos actualizar añadiendo el número de canal (ch) a la dirección base (0xC0). En segundo lugar, tenemos que extraer el valor correcto del registro de nuestra caché utilizando ch como índice de la matriz. 4.2.1.2.3 Funciones,de,registros,de,operador, Para actualizar los registros de operador se sigue el mismo procedimiento que para los registros de canal, con la diferencia de que los registros no son consecutivos como los de canal. Para ello, utilizamos una matriz que asigne el número de operador al desplazamiento de memoria. uint8_t op_map[YM3812_NUM_OPERATORS] = { 0,1,2,3,4,5,8,9,10,11,12,13, 16,17,18,19,20,21 }; Por ejemplo, si quisiéramos modificar el ataque: void regOpAttack( uint8_t op, uint8_t val ){ sendData(0x60+op_map[op], SET_BITS( reg_60[op], 0b00001111, 4, val ) ); } Ahora en lugar de añadir el número de operador a la dirección base, añadimos el offset mapeado de nuestro array. 4.2.1.3 Funciones principales En el archivo principal, encontramos dos funciones: setup() y loop(). 4.2.1.3.1 Funcio8 n,setup(), En esta función inicializamos las variables y configuramos las propiedades del sonido que queremos crear. void setup() { // Configurar pines como salida pinMode(YM_WR, OUTPUT); pinMode(YM_A0, OUTPUT); pinMode(YM_IC, OUTPUT); pinMode(YM_LATCH, OUTPUT); pinMode(YM_CS, OUTPUT); 41 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 pinMode(DATA_LED, OUTPUT); // Inicialización de SPI para Arduino Uno SPI.begin(); SPI.setClockDivider(SPI_CLOCK_DIV16); PROC_YM3812.reset(); uint8_t op1_index, op2_index; // Configurar el patch for( uint8_t ch=0; ch<YM3812_NUM_CHANNELS; ch++){ // Mismo patch para todos los canales op1_index = PROC_YM3812.channel_map[ch]; op2_index = op1_index + 3; // Siempre 3 más //Configuración del canal PROC_YM3812.regChAlgorithm( ch, 0x1 ); // Síntesis aditiva PROC_YM3812.regChFeedback( ch, 0x0 ); // Feedback //Configuración del operador 1 PROC_YM3812.regOpAttack( op1_index, 0xB ); PROC_YM3812.regOpDecay( op1_index, 0x6 ); PROC_YM3812.regOpSustain( op1_index, 0xA ); PROC_YM3812.regOpRelease( op1_index, 0x2 ); PROC_YM3812.regOpLevel( op1_index, 0x0 ); // 0 – más alto, 64 (0x40) más suave PROC_YM3812.regOpWaveForm( op1_index, 0x1 ); //Configuración del operador 2 PROC_YM3812.regOpAttack( op2_index, 0xB ); PROC_YM3812.regOpDecay( op2_index, 0x6 ); PROC_YM3812.regOpSustain( op2_index, 0xA ); PROC_YM3812.regOpRelease( op2_index, 0x2 ); PROC_YM3812.regOpLevel( op2_index, 0x0 ); // 0 – más alto, 64 (0x40) más suave PROC_YM3812.regOpWaveForm( op2_index, 0x1 ); } } 4.2.1.3.2 Funcio8 n,loop(), En esta función finalmente reproducimos las cuatro notas del acorde. void loop() { uint8_t op1_index, op2_index; //Apaga todas las notas PROC_YM3812.regKeyOn( 0, false ); // Apaga canal 0 PROC_YM3812.regKeyOn( 1, false ); // Apaga canal 1 PROC_YM3812.regKeyOn( 2, false ); // Apaga canal 2 PROC_YM3812.regKeyOn( 3, false ); // Apaga canal 3 //Establece las octavas de las notas PROC_YM3812.regFrqBlock( 0, 4); //Cuarta octava PROC_YM3812.regFrqBlock( 1, 4); PROC_YM3812.regFrqBlock( 2, 4); PROC_YM3812.regFrqBlock( 3, 4); Desarrollo del Prototipo 48 PATCH_WAVEFORM ]>>5)<<0) ); } } 4.2.4.2 Evento handleProgramChange Este evento se ejecuta cada vez que un dispositivo MIDI indica que quiere seleccionar un patch diferente y obtenemos un mensaje MIDI Program Change asociado a un número de la lista de patches. Dependiendo de ese número, lo asociaremos a uno de los 16 instrumentos representantes de los bloques que tenemos cargados en la memoria de Arduino. Esta función se muestra al final de este capítulo en el código completo. 4.2.5 Programa final Una vez implementados todos los módulos, la estructura final de los ficheros queda así: Figura 4-6. Estructura final de los ficheros Se ha añadido un nuevo fichero “YMDefs.h” en el que se definen los arrays que configuran las características del canal y operador, además de las características del patch general. El programa final se muestra a continuación. 49 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 4.2.5.1 YM3812_PolySmart4.ino #include "Arduino.h" #include "YM3812.h" #include <MIDI.h> #include <SPI.h> #include "instruments.h" YM3812 PROC_YM3812; #define YM_WR 0b00100000 #define YM_A0 0b01000000 #define YM_IC 0b00000100 #define YM_LATCH 0b00001000 #define YM_CS 0b00010000 #define DATA_LED 0b10000000 #define MAX_INSTRUMENTS 16 uint8_t inst_patch_index[ MAX_INSTRUMENTS ]; PatchArr inst_patch_data[ MAX_INSTRUMENTS ]; MIDI_CREATE_INSTANCE( HardwareSerial, Serial, MIDI ); unsigned long lastPatchChange = 0; bool usingFirstPatch = true; int currentPatchIndex = 0; void handleNoteOn( byte channel, byte midiNote, byte velocity ){ PROC_YM3812.noteOn( midiNote ); } void handleNoteOff( byte channel, byte midiNote, byte velocity ){ PROC_YM3812.noteOff( midiNote ); } void handleProgramChange(byte ch, byte number) { for (uint8_t ch = 0; ch < YM3812_NUM_CHANNELS; ch++) { if(number>=0 && number <= 10) { PatchArr temp; memcpy(temp, patches[0], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else { if(number>=11 && number <= 21) { PatchArr temp; memcpy(temp, patches[1], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=22 && number <= 31) Desarrollo del Prototipo 50 { PatchArr temp; memcpy(temp, patches[2], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=32 && number <= 44) { PatchArr temp; memcpy(temp, patches[3], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=45 && number <= 55) { PatchArr temp; memcpy(temp, patches[4], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=56 && number <= 66) { PatchArr temp; memcpy(temp, patches[5], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=67 && number <= 77) { PatchArr temp; memcpy(temp, patches[6], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=78 && number <= 88) { PatchArr temp; memcpy(temp, patches[7], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=89 && number <= 99) { PatchArr temp; memcpy(temp, patches[8], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=100 && number <= 110) { PatchArr temp; memcpy(temp, patches[9], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=111 && number <= 121) { 51 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 PatchArr temp; memcpy(temp, patches[10], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=122 && number <= 132) { PatchArr temp; memcpy(temp, patches[11], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=133 && number <= 143) { PatchArr temp; memcpy(temp, patches[12], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=144 && number <= 154) { PatchArr temp; memcpy(temp, patches[13], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ if(number>=155 && number <= 165) { PatchArr temp; memcpy(temp, patches[14], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } else{ PatchArr temp; memcpy(temp, patches[15], sizeof(PatchArr)); PROC_YM3812.chSendPatch(ch, temp); } } } } } } } } } } } } } } } Desarrollo del Prototipo 52 } } void setup(void) { PROC_YM3812.reset(); for( uint8_t ch=0; ch<YM3812_NUM_CHANNELS; ch++){ PROC_YM3812.chSendPatch(ch,ym_patch_0); } MIDI.setHandleNoteOn( handleNoteOn ); MIDI.setHandleNoteOff( handleNoteOff ); MIDI.setHandleProgramChange(handleProgramChange); MIDI.begin(); } void loop() { while( MIDI.read(0) ){} // } 53 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 4.2.5.2 YM3812.cpp #include "Arduino.h" #include <SPI.h> #include "YM3812.h" #include "YMDefs.h" #define YM_WR 0b00100000 #define YM_A0 0b01000000 #define YM_IC 0b00000100 #define YM_LATCH 0b00001000 #define YM_CS 0b00010000 #define DATA_LED 0b10000000 YM3812::YM3812(){ DDRD |= (YM_IC | YM_A0 | YM_WR | YM_LATCH | YM_CS); PORTD &= ~YM_LATCH; PORTD |= (YM_WR | YM_CS); DDRD |= DATA_LED; PORTD &= ~DATA_LED; } void YM3812::reset(){ SPI.begin(); PORTD &= ~YM_IC; delay(10); // PORTD |= YM_IC; delay(10); reg_01 = reg_08 = reg_BD = 0; for( uint8_t ch = 0; ch < YM3812_NUM_CHANNELS; ch++ ){ reg_A0[ch] = reg_B0[ch] = reg_C0[ch] = 0; } for( uint8_t op = 0; op < YM3812_NUM_OPERATORS; op++ ){ reg_20[op] = reg_40[op] = reg_60[op] = reg_80[op] = reg_E0[op] = 0; } regWaveset( 1 ); } void YM3812::sendData( uint8_t reg, uint8_t val ){ PORTD |= DATA_LED; PORTD &= ~YM_CS; PORTD &= ~YM_A0; SPI.transfer(reg); PORTD |= YM_LATCH; delayMicroseconds(1); PORTD &= ~YM_LATCH; PORTD &= ~YM_WR; delayMicroseconds(10); PORTD |= YM_WR; delayMicroseconds(10); PORTD |= YM_A0; Desarrollo del Prototipo 54 SPI.transfer(val); PORTD |= YM_LATCH; delayMicroseconds(1); PORTD &= ~YM_LATCH; PORTD &= ~YM_WR; delayMicroseconds(10); PORTD |= YM_WR; delayMicroseconds(10); PORTD |= YM_CS; PORTD &= ~DATA_LED; } uint8_t YM3812::chGetNext(){ uint8_t on_channel = 0xFF; uint8_t off_channel = 0xFF; unsigned long oldest_on_time = millis(); unsigned long oldest_off_time = millis(); for( uint8_t ch=0; ch < num_channels; ch++ ){ if( channel_states[ch].note_state ){ if( channel_states[ch].state_changed < oldest_on_time ){ oldest_on_time = channel_states[ch].state_changed; on_channel = ch; } } else { if( channel_states[ch].state_changed < oldest_off_time ){ oldest_off_time = channel_states[ch].state_changed; off_channel = ch; } } } if( off_channel < 0xFF ){ return( off_channel ); } return( on_channel ); } void YM3812::chPlayNote( uint8_t ch, uint8_t midiNote ){ regKeyOn( ch, 0 ); if( midiNote > 114 ) return; if( midiNote < 19 ){ regFrqBlock( ch, 0 ); regFrqFnum( ch, FRQ_SCALE[midiNote] ); } else { regFrqBlock( ch, (midiNote - 19) / 12 ); regFrqFnum( ch, FRQ_SCALE[((midiNote - 19) % 12) + 19] ); } regKeyOn( ch, 1 ); } 55 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 void YM3812::noteOn( uint8_t midiNote ){ last_channel = chGetNext(); channel_states[ last_channel ].midi_note = midiNote; channel_states[ last_channel ].note_state = true; channel_states[ last_channel ].state_changed = millis(); chPlayNote( last_channel, midiNote ); } void YM3812::noteOff( uint8_t midiNote ){ for( uint8_t ch = 0; ch<num_channels; ch++ ){ if( channel_states[ch].midi_note == midiNote ){ channel_states[ ch ].state_changed = millis(); channel_states[ ch ].note_state = false; regKeyOn( ch, 0 ); } } } void YM3812::chSendPatch( byte ch, const PatchArr &patch ){ byte mem_offset, patch_offset; sendData( 0xC0+ch, ((patch[PATCH_FEEDBACK]>>4)<<1) | (patch[PATCH_ALGORITHM]>>6) ); for( uint8_t op = 0; op<2; op++ ){ mem_offset = op_map[channel_map[ch] + op*3]; patch_offset = PATCH_OP_SETTINGS * op; sendData( 0x20+mem_offset, ((patch[patch_offset + PATCH_TREMOLO ]>>6)<<7) | ((patch[patch_offset + PATCH_VIBRATO ]>>6)<<6) | ((patch[patch_offset + PATCH_PERCUSSIVE_ENV ]>>6)<<5) | ((patch[patch_offset + PATCH_ENV_SCALING ]>>6)<<4) | ((patch[patch_offset + PATCH_FREQUENCY_MULT ]>>3)<<0) ); sendData( 0x40+mem_offset, ((patch[patch_offset + PATCH_LEVEL_SCALING ]>>5)<<6) | (patch[patch_offset + PATCH_LEVEL ]>>1) ); sendData( 0x60+mem_offset, ((patch[patch_offset + PATCH_ATTACK ]>>3)<<4) | ((patch[patch_offset + PATCH_DECAY ]>>3)<<0) ); sendData( 0x80+mem_offset, (0xF-((patch[patch_offset + PATCH_SUSTAIN_LEVEL ]>>3)<<4)) | ((patch[patch_offset + PATCH_RELEASE_RATE ]>>3)<<0) ); sendData( 0xE0+mem_offset, ((patch[patch_offset + PATCH_WAVEFORM ]>>5)<<0) ); } } Desarrollo del Prototipo 56 4.2.5.3 YM3812.h #ifndef YM3812_H #define YM3812_H #include "YMDefs.h" #define YM3812_NUM_CHANNELS 9 #define YM3812_NUM_OPERATORS 18 #define SET_BITS( regVal, mask, offset, newVal) ((regVal) = ((regVal) & (~((mask)<<(offset)))) | (((newVal) & (mask)) << (offset))) #define GET_BITS( regVal, mask, shift ) ( ((regVal) & (mask)) >> (shift) ) struct YM_Channel{ uint8_t midi_note = 0; bool note_state = false; unsigned long state_changed; }; class YM3812 { private: uint8_t reg_01 = 0; uint8_t reg_08 = 0; uint8_t reg_BD = 0; uint8_t reg_A0[YM3812_NUM_CHANNELS] = {0,0,0,0,0,0,0,0,0}; uint8_t reg_B0[YM3812_NUM_CHANNELS] = {0,0,0,0,0,0,0,0,0}; uint8_t reg_C0[YM3812_NUM_CHANNELS] = {0,0,0,0,0,0,0,0,0}; uint8_t reg_20[YM3812_NUM_OPERATORS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t reg_40[YM3812_NUM_OPERATORS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t reg_60[YM3812_NUM_OPERATORS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t reg_80[YM3812_NUM_OPERATORS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t reg_E0[YM3812_NUM_OPERATORS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t op_map[YM3812_NUM_OPERATORS] = { 0,1,2,3,4,5,8,9,10,11,12,13,16,17,18,19,20,21 }; const unsigned int FRQ_SCALE[31] = { 0x0AD, 0x0B7, 0x0C2, 0x0CD, 0x0D9, 0x0E6, 0x0F4, 0x102, 0x112, 0x122, 0x133, 0x145, 0x159, 0x16D, 0x183, 0x19A, 0x1B2, 0x1CC, 0x1E8, 0x205, 0x224, 0x244, 0x267, 0x28B, 0x2B2, 0x2DB, 0x306, 0x334, 0x365, 0x399, 0x3CF }; uint8_t num_channels = YM3812_NUM_CHANNELS; YM_Channel channel_states[YM3812_NUM_CHANNELS]; uint8_t last_channel = 0; public: uint8_t channel_map[YM3812_NUM_CHANNELS] = { 0,1,2,6,7,8,12,13,14 }; 57 Diseño y realización de un sintetizador MIDI basado en el sintetizador FM de Yamaha OPL2 public: YM3812(); void reset(); void sendData(uint8_t reg, uint8_t val); void noteOn( uint8_t midiNote ); void noteOff( uint8_t midiNote ); uint8_t chGetNext(); void chPlayNote( uint8_t ch, uint8_t midiNote ); void regWaveset( uint8_t val ){ SET_BITS( reg_01, 0b00000001, 5, val ); sendData(0x01, reg_01); } void regSpeechSynthesis( uint8_t val ){ SET_BITS( reg_08, 0b00000001, 7, val ); sendData(0x08, reg_08); } void regKeySplit( uint8_t val ){ SET_BITS( reg_08, 0b00000001, 6, val ); sendData(0x08, reg_08); } void regTremoloDepth( uint8_t val ){ sendData(0xBD, SET_BITS( reg_BD, 0b00000001, 7, val ) ); } void regVibratoDepth( uint8_t val ){ sendData(0xBD, SET_BITS( reg_BD, 0b00000001, 6, val ) ); } void regChAlgorithm( uint8_t ch, uint8_t val ){ sendData(0xC0+ch, SET_BITS( reg_C0[ch], 0b00000001, 0, val ) ); } void regChFeedback( uint8_t ch, uint8_t val ){ sendData(0xC0+ch, SET_BITS( reg_C0[ch], 0b00000111, 1, val ) ); } void regKeyOn( uint8_t ch, uint8_t val ){ sendData(0xB0+ch, SET_BITS( reg_B0[ch], 0b00000001, 5, val ) ); } void regFrqBlock( uint8_t ch, uint8_t val ){ sendData(0xB0+ch, SET_BITS( reg_B0[ch], 0b00000111, 2, val ) ); } void regFrqFnum( uint8_t ch, uint16_t frequency ){ sendData( 0xA0+ch, SET_BITS( reg_A0[ch], 0b11111111, 0, frequency & 0xFF ) ); sendData( 0xB0+ch, SET_BITS( reg_B0[ch], 0b00000011, 0, frequency >> 8 ) ); } void regOpTremolo( uint8_t op, uint8_t val ){ sendData(0x20+op_map[op], SET_BITS( reg_20[op], 0b00000001, 7, val ) ); } void regOpVibrato( uint8_t op, uint8_t val ){ sendData(0x20+op_map[op], SET_BITS( reg_20[op], 0b00000001, 6, val ) ); } void regOpPercussiveEnv( uint8_t op, uint8_t val ){ sendData(0x20+op_map[op], SET_BITS( reg_20[op], 0b00000001, 5, val ) ); } void regOpEnvScaling( uint8_t op, uint8_t val ){ sendData(0x20+op_map[op], SET_BITS( reg_20[op], 0b00000001, 4, val ) ); } void regOpFrequencyMult( uint8_t op, uint8_t val ){ sendData(0x20+op_map[op], SET_BITS( reg_20[op], 0b00001111, 0, val ) ); } 65 6 CONCLUSIONES ara concluir el proyecto, se recalcan los aspectos más valiosos ofrecidos por este sistema. Su desarrollo no solo ha permitido la construcción de un sistema MIDI basado en síntesis FM con el chip OPL2, sino también una comprensión profunda de los distintos elementos que intervienen en su diseño, tanto a nivel teórico como práctico. Uno de los principales aprendizajes ha sido la arquitectura y funcionamiento del YM3812 (OPL2), un chip histórico en la generación de sonido por modulación de frecuencia que, a pesar de sus limitaciones comparado con sistemas modernos, ofrece una gran versatilidad sonora gracias a su enfoque en canales independientes y operadores moduladores y portadores. El estudio de su arquitectura interna, modos de operación y mapeo de registros ha sido clave para comprender cómo se generan los timbres característicos de la síntesis FM. Además, se profundizó en el protocolo MIDI, entendiendo su estructura de mensajes, su codificación en bytes de estado y datos, y su forma de controlar instrumentos musicales de manera digital. Este conocimiento ha sido esencial para adaptar las señales de control y permitir que el microcontrolador gestiones la interacción entre el usuario y el chip de audio. Otro aspecto fundamental del proyecto fue el desarrollo del prototipo, implicando tanto la programación del firmware en Arduino como el montaje físico del hardware, consolidando habilidades en diseño de circuitos, soldadura y programación. Un aprendizaje clave fue la importancia de la integración del sistema en una única PCB, lo cual no solo permitió reducir espacio, sino también evitar errores por conexiones manuales o demasiado cableado. Finalmente, este proyecto ha servido como una plataforma de aprendizaje en muchas áreas distintas como síntesis de sonido, electrónica digital y analógica, programación y desarrollo de circuitos. P 67 REFERENCIAS [1] C. Roads, The Computer Musical Turorial., Cambridge, MA: MIT Press, 1996. [2] E. R. Miranda & J. Castet, Guide to brain-computer music interfacing, Springer, 2014. [3] A. Unici, Digital audio processing fundamentals, Springer Nature Switzerland AG, 2022. [4] Things Made Simple, https://www.thingsmadesimple.com/, 2023 [5] Yamaha, YM3812 Application Manual, 1994