Tocamos música de Mario en el altavoz del sistema

Mario  Notas

Prefacio


Hola Mundo!

Ya alrededor de 3 años quiero escribir algo en Habr, pero no había ningún tema sobre el cual publicar una publicación. Eso fue hasta que tuve que aprender un poco sobre el trabajo del temporizador del sistema y el altavoz del sistema para el trabajo de laboratorio. Después de buscar un poco en Internet, no encontré nada práctico: algo estaba escrito en un lenguaje demasiado complicado, algo no era particularmente significativo. Conseguí un buen libro, toda la noche e intenté tocar el conocido tema del juego de Mario. Continuación justo debajo del corte, parece que lo tienes aquí.

Descargo de responsabilidad


El código se escribe como se escribe. El autor no es un genio de la programación, sino solo un estudiante, pero, sin embargo, intentó escribir el código más legible y comprensible. Todo se escribió en Borland C y se probó en DOSBox solo porque no hay dos establecidos y no quiero arruinarlo con un reloj en tiempo real.

De vuelta en 2012Loiqig Ya escribí una versión más genial , pero, como me pareció, prestó poca atención a la teoría.

Además, el autor (es decir, I) tiene 4 años de educación musical y fue pobre en solfeo (notación musical).

Poco de teoría


Hace mucho tiempo, cuando el procesador Intel 8086 era popular, y la PC de IBM no planteaba ninguna pregunta, Intel 8253 se utilizó en estas mismas PC de IBM y computadoras compatibles: un temporizador y un contador de intervalos. En las computadoras modernas, el puente sur se ocupa de esto ( Fuente: Wikipedia ).

Ejemplo de lógica Intel 8253 PIT:

Diagrama lógico de Intel 8253. Fig.  1

Diagrama lógico de Intel 8253. Fig.  2

Como puede ver en la imagen de arriba, el temporizador está conectado a la línea IRQ0. Genera una interrupción de 8 h 18.2 veces por segundo.

El temporizador consta de 3 contadores (COUNTER0-2), que funcionan independientemente uno del otro.

No importa cuán extraño pueda ser, cada contador hace su trabajo. En las computadoras modernas, el primer canal cuenta la hora del día. Usando el segundo canal, la DRAM se regenera. Usando el tercer canal, puede hacer un generador de números pseudoaleatorio y afeitarlo con un altavoz del sistema.

Cada canal tiene 6 modos de funcionamiento:

  • Modo 0 - interrupción de cuenta terminal
  • Modo 1 - Multivibrador en espera programable
  • Modo 2 - Generador de frecuencia de pulso
  • Modo 3 - generador de meandros
  • Modo 4: luz estroboscópica generada por software
  • Modo 5: luz estroboscópica basada en hardware

Vamos a ir al grano


Entonces, aprendimos una pequeña teoría sobre el temporizador del sistema, aprendimos que el tercer canal está conectado al altavoz del sistema. Todo parece estar bien. ¿Cómo usar esto para tocar un tema de Mario? Aún no está claro.

Cada contador (canal) se programa por separado. Ya hemos decidido que necesitas usar el tercero. Como puede ver en la imagen de arriba, el altavoz está conectado a la salida OUT. Al mismo tiempo, está conectado al puerto 61h, con el que podemos controlar el altavoz. El primer bit (menos significativo) está conectado a la entrada Gate2 y determina si el contador está funcionando o no. El segundo bit inicia el altavoz.

Basado en esta teoría, el frente del trabajo para reproducir sonido se vuelve claro:

  • Programamos CLOCK2 a la frecuencia que necesitamos (más sobre eso más adelante)
  • Use los primeros dos bits de 61h para encender el altavoz

Dado que el altavoz predeterminado del sistema solo puede reproducir sonidos de una sola voz (bueno, como era en los viejos teléfonos con botón pulsador, donde se tocaba el boom), necesitamos obtener la frecuencia de cada nota.

Tabla de ratios de notas y frecuencias


, / , / 2.

Para establecer la frecuencia deseada para nuestro contador, debe usar una fórmula determinada: 1193182 / N Hz, donde 1193182 es la frecuencia del temporizador (1.193182 MHz si es correcto), N es la frecuencia de la nota que decidió emitir al altavoz.

//   
// _freq —  
// _dur —  
// _del —    
void play_sound(int _freq, int _dur, int _del)
{
	outp(0x43, 0xb6); //     2 ()

	int timer_soundFreq = TIMER_FREQUENCY/_freq; //    
                                                    //.
                                                   // TIMER_FREQUENCY = 1193182

	//     
	outp(0x42, timer_delay & 0x00ff); //   
	outp(0x42, (timer_delay & 0xff00) >> 8); //  


	outp(0x61, inp(0x61) | 3); //  


	delay(_dur); //  ,     ,
		    //   

	outp(0x61, inp(0x61) & 0xfc); //  

	delay(_del); //     
}

La función principal es terriblemente simple y, francamente, poco optimizada, pero no cambia la esencia del asunto.

int main(int argc, char const *argv[])
{
	for (size_t i = 0; i < N; ++i) // N —  ,  
	{
		play_sound(FREQUENCY[i], DURATION[i], DELAY[i]);
	}
	return 0;
}

¿Y qué hay de nuestro Mario?


Aprendimos a tocar sonidos a través de la dinámica del sistema. ¡Multa! Pero, ¿cómo tocamos una canción de Mario?

Usando la magia de googlear encontramos las notas:

Tonos de partituras de Mario
.

A continuación, deberá recordar el curso de la notación musical y escribir cada nota:

Las notas escritas y su dimensión:
2 — 1/4
2 — 1/4
2 — 1/8
2 — 1/8
2 — 1/4

2 — 1/4
— 1/4

2 — 1/4
— 1/4
— 1/4

— 1/4
— 1/4
() — 1/8
— 1/4

— 1/4
2 — 1/4
2 — 1/4
2 — 1/4
2 — 1/8
2 --1/8

2 — 1/4
2 — 1/8
2 — 1/8
— 1/8

2 — 1/4
()2 — 1/4
()2 — 1/8
()2 — 1/4
()2 — 1/8

() — 1/4
() — 1/4
2 — 1/4
— 1/4
2 — 1/4
2 — 1/4

2 — 1/4
()2 — 1/4
()2 — 1/8
2 — 1/4
2 — 1/4

3 — 1/4
3 — 1/8
3 — 1/4

Vale la pena señalar que introduje varios supuestos. En una melodía real, se tocan dos notas simultáneamente. No quería sincronizar los dos dosboxes, así que jugué con la misma nota.

Armados con una paciencia aún mayor, traducimos cada nota en frecuencias y recopilamos matrices de frecuencias y duraciones:

// Mario -- 43
int FREQUENCY[] = {659.255, 659.255, 659.255, 523.251, 659.255, 783.991, 391.995, 523.251, 391.995, 329.628, 440, 493.883, 466.164, 440, 391.995, 659.255, 783.991, 880, 698.456, 783.991, 659.255, 523.251, 587.33, 987.767, 783.991, 680.255, 698.456, 622.254, 680.255, 415.305, 466.164, 523.251, 440, 523.251, 587.33, 783.991, 739.989, 729.989, 587.33, 659.255, 1046.502, 1046.502, 1046.502};

int DURATION[] = {300, 300, 160, 160, 300, 300, 300, 300, 300, 300, 300, 300, 160, 300, 300, 300, 300, 300, 160, 160, 300, 160, 160, 160, 300, 300, 160, 300, 160, 300, 300, 300, 300, 300, 300, 300, 300, 160, 300, 300, 300, 160, 300};

int DELAY[] = {35, 35, 50, 35, 35, 350, 200, 35, 35, 200, 35, 35, 35, 200, 35, 35, 35, 35, 35, 200, 35, 35, 35, 35, 35, 35, 35, 35, 200, 35, 35, 35, 35, 35, 200, 35, 35, 35, 35, 200, 35, 35, 0};

En esta melodía conté 43 notas. También vale la pena señalar aquí que elegí el retraso entre dos notas adyacentes de oído. Resultó un poco más lento que en el original.

Conclusión


En conclusión, quiero decir que trabajar con hardware a veces resulta más interesante que escribir un montón de código de alto nivel.

Si alguien de repente quiere mejorar mi melodía o escribir algo propio, puede hacer un comentario.

PD


Si decides que quieres jugar con la dinámica del sistema y no conoces los conceptos básicos de la notación musical, entonces debajo del spoiler puedes encontrar un par de pistas.

Pistas musicales
. .
.

All Articles