Nous jouons de la musique de Mario sur le haut-parleur du système

Mario  Remarques

Préface


Bonjour le monde!

Déjà environ 3 ans, je veux écrire quelque chose sur Habr, mais il n'y avait aucun sujet sur lequel poster un post. C'était jusqu'à ce que je devais en apprendre un peu plus sur le travail du minuteur et du haut-parleur du système pour les travaux de laboratoire. Après avoir parcouru un peu Internet, je n'ai rien trouvé de pratique: quelque chose était écrit dans une langue trop compliquée, quelque chose n'était pas particulièrement significatif. J'ai eu un bon livre, toute la nuit et j'ai essayé de jouer le thème bien connu du jeu Mario. Continuation juste sous la coupe, on dirait que vous l'avez ici.

Avertissement


Le code est écrit tel qu'il est écrit. L'auteur n'est pas un génie de la programmation, mais juste un étudiant, mais néanmoins, il a essayé d'écrire le code le plus lisible et le plus compréhensible. Tout a été écrit en Borland C et a été testé dans DOSBox uniquement parce qu'il n'y a pas de dos établi et je ne veux pas vraiment bousiller avec une horloge en temps réel.

Retour en 2012Loiqig déjà écrit une version plus cool , mais, comme il me semblait, prêtait peu d'attention à la théorie.

De plus, l'auteur (c.-à-d. I) a 4 ans d'éducation musicale et était pauvre en solfège (notation musicale).

Un peu de théorie


Il y a longtemps, lorsque le processeur Intel 8086 était populaire et que le PC IBM ne soulevait aucune question, Intel 8253 était utilisé dans ces mêmes PC IBM et ordinateurs compatibles - un minuteur et un compteur d'intervalles. Dans les ordinateurs modernes, le pont sud s'en occupe ( Source: Wikipedia ).

Exemple de logique Intel 8253 PIT:

Diagramme logique d'Intel 8253. Fig.  1

Diagramme logique d'Intel 8253. Fig.  2

Comme vous pouvez le voir sur l'image ci-dessus, le minuteur est connecté à la ligne IRQ0. Il génère une interruption de 8h 18,2 fois par seconde.

La minuterie se compose de 3 compteurs (COUNTER0-2), qui fonctionnent indépendamment les uns des autres.

Aussi étrange que cela puisse être, chaque compteur fait son travail. Dans les ordinateurs modernes, le premier canal compte l'heure de la journée. En utilisant le deuxième canal, la DRAM est régénérée. En utilisant le troisième canal, vous pouvez créer un générateur de nombres pseudo-aléatoires et le raser avec un haut-parleur système.

Chaque canal a 6 modes de fonctionnement:

  • Mode 0 - interruption du compte du terminal
  • Mode 1 - Multivibrateur de secours programmable
  • Mode 2 - Générateur de fréquence d'impulsion
  • Mode 3 - générateur de méandre
  • Mode 4 - Stroboscope généré par logiciel
  • Mode 5 - stroboscope matériel

Nous allons passer aux choses sérieuses


Donc, nous avons appris un peu de théorie sur la minuterie du système, appris que le troisième canal est connecté au haut-parleur du système. Tout semble cool. Comment l'utiliser pour jouer un thème de Mario? Ce n’est pas encore clair.

Chaque compteur (canal) est programmé séparément. Nous avons déjà décidé que vous devez utiliser le troisième. Comme vous pouvez le voir sur l'image ci-dessus, le haut-parleur est connecté à la sortie OUT. Dans le même temps, il est connecté au port 61h, avec lequel nous pouvons contrôler le haut-parleur. Le premier bit (le moins significatif) est connecté à l'entrée Gate2 et détermine si le compteur fonctionne ou non. Le deuxième bit démarre l'enceinte.

Sur la base de cette théorie, le front du travail pour jouer du son devient clair:

  • Nous programmons CLOCK2 à la fréquence dont nous avons besoin (plus de détails plus tard)
  • Utilisez les deux premiers bits de 61h pour allumer le haut-parleur

Étant donné que le haut-parleur système par défaut ne peut reproduire que des sons à une seule voix (enfin, comme c'était le cas sur les anciens téléphones à bouton-poussoir, où ils jouaient le boomer), nous devons obtenir la fréquence de chaque note.

Tableau des ratios de notes et fréquences


, / , / 2.

Afin de définir la fréquence souhaitée pour notre compteur, vous devez utiliser une certaine formule: 1193182 / N Hz, où 1193182 est la fréquence du minuteur (1,193182 MHz pour être correct), N est la fréquence de la note que vous avez décidé d'émettre vers le haut-parleur.

//   
// _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 fonction principale est terriblement simple et, franchement, mal optimisée, mais elle ne change pas l'essence de la question.

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;
}

Et notre mario?


Nous avons appris à jouer des sons grâce à la dynamique du système. Bien! Mais comment jouer un morceau de Mario?

En utilisant la magie de la recherche sur Google, nous trouvons les notes:

Partitions de sonneries de Mario
.

Ensuite, vous devrez vous souvenir du cours de notation musicale et écrire chaque note:

Les notes écrites et leur dimension:
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

Il convient de noter que j'ai introduit plusieurs hypothèses. Dans une vraie mélodie, deux notes sont jouées simultanément. Je ne voulais pas synchroniser les deux dosboxes, j'ai donc joué sur la même note.

Armés d'une patience encore plus grande, nous traduisons chaque note en fréquences et collectons des tableaux de fréquences et de durées:

// 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};

Dans cette mélodie, j'ai compté 43 notes. Il convient également de noter ici que j'ai choisi le délai entre deux notes adjacentes à l'oreille. Il s'est avéré un peu plus lent que dans l'original.

Conclusion


En conclusion, je tiens à dire que travailler avec du matériel s'avère parfois plus intéressant que d'écrire un tas de code de haut niveau.

Si quelqu'un veut soudainement améliorer ma mélodie ou écrire quelque chose qui lui est propre, alors vous êtes invités à commenter.

PS


Si vous décidez que vous voulez jouer sur la dynamique du système et que vous ne connaissez pas les bases de la notation musicale, alors sous le spoiler vous pouvez trouver quelques indices.

Notes musicales
. .
.

All Articles