Tocamos música de Mario no alto-falante do sistema

Mario  Notas

Prefácio


Olá Mundo!

Já há cerca de três anos, quero escrever algo sobre Habr, mas não havia nenhum tópico sobre o qual publicar uma postagem. Isso foi até eu precisar aprender um pouco sobre o trabalho do cronômetro e do alto-falante do sistema para o trabalho de laboratório. Tendo vasculhado um pouco a Internet, não achei nada prático: algo foi escrito em uma linguagem muito complicada, algo não era particularmente significativo. Eu recebi um bom livro a noite toda e tentei jogar o tema bem conhecido do jogo Mario. Continuação logo abaixo do corte, parece que você o tem aqui.

aviso Legal


O código está escrito como está escrito. O autor não é um gênio da programação, mas apenas um estudante, mas, no entanto, tentou escrever o código mais legível e compreensível. Tudo foi escrito em Borland C e foi testado no DOSBox apenas porque não há dos estabelecidos e eu realmente não quero estragar o relógio em tempo real.

Em 2012Loiqig já escrevi uma versão mais legal , mas, como me pareceu, prestou pouca atenção à teoria.

Além disso, o autor (ou seja, I) tem 4 anos de educação musical e era pobre em solfeggio (notação musical).

Pouco de teoria


Há muito tempo, quando o processador Intel 8086 era popular e o PC IBM não levantava dúvidas, o Intel 8253 era usado nesses mesmos PCs IBM e computadores compatíveis - um timer e um contador de intervalo. Nos computadores modernos, a ponte sul lida com isso ( Fonte: Wikipedia ).

Exemplo de lógica Intel 8253 PIT:

Diagrama lógico da Intel 8253. Fig.  1 1

Diagrama lógico da Intel 8253. Fig.  2

Como você pode ver na imagem acima, o timer está conectado à linha IRQ0. Ele gera uma interrupção de 8h 18,2 vezes por segundo.

O temporizador consiste em 3 contadores (COUNTER0-2), que operam independentemente um do outro.

Por mais estranho que seja, cada balcão faz seu trabalho. Nos computadores modernos, o primeiro canal conta a hora do dia. Usando o segundo canal, a DRAM é regenerada. Usando o terceiro canal, você pode criar um gerador de números pseudo-aleatórios e raspá-lo com um alto-falante do sistema.

Cada canal possui 6 modos de operação:

  • Modo 0 - interrupção da conta do terminal
  • Modo 1 - Multivibrador programável em espera
  • Modo 2 - Gerador de Freqüência de Pulso
  • Modo 3 - gerador de meandros
  • Modo 4 - estroboscópio gerado por software
  • Modo 5 - estroboscópio baseado em hardware

Vamos ao que interessa


Então, aprendemos um pouco de teoria sobre o temporizador do sistema, aprendemos que o terceiro canal está conectado ao alto-falante do sistema. Tudo parece ser legal. Como usar isso para reproduzir um tema de Mario? Ainda não está claro.

Cada contador (canal) é programado separadamente. Já decidimos que você precisa usar o terceiro. Como você pode ver na imagem acima, o alto-falante está conectado à saída OUT. Ao mesmo tempo, está conectado à porta 61h, com a qual podemos controlar o alto-falante. O primeiro bit (menos significativo) é conectado à entrada do Gate2 e determina se o contador está funcionando ou não. O segundo bit inicia o alto-falante.

Com base nessa teoria, a frente do trabalho para tocar som fica clara:

  • Programamos o CLOCK2 na frequência que precisamos (mais sobre isso mais tarde)
  • Use os dois primeiros bits de 61h para ligar o alto-falante

Como o alto-falante padrão do sistema pode tocar apenas sons de uma voz (bem, como era nos telefones antigos, onde o boomer era tocado), precisamos obter a frequência de cada nota.

Tabela de proporções de notas e frequências


, / , / 2.

Para definir a frequência desejada para o nosso contador, você precisa usar uma certa fórmula: 1193182 / N Hz, em que 1193182 é a frequência do temporizador (1,193182 MHz, se estiver correto), N é a frequência da nota que você decidiu enviar para o alto-falante.

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

A função principal é terrivelmente simples e, francamente, mal otimizada, mas não altera a essência da questão.

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

E o nosso mario?


Aprendemos a tocar sons através da dinâmica do sistema. Bem! Mas como tocamos uma música de Mario?

Usando a magia do Google, encontramos as notas:

Toques para partituras de Mario
.

Em seguida, você terá que se lembrar do curso da notação musical e escrever cada nota:

As notas escritas e sua dimensão:
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 a pena notar que introduzi várias suposições. Em uma melodia real, duas notas são tocadas simultaneamente. Como não queria sincronizar as duas caixas, joguei na mesma nota.

Munidos de uma paciência ainda maior, traduzimos cada nota em frequências e coletamos conjuntos de frequências e duraçõ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};

Nesta melodia, contei 43 notas. Também é interessante notar aqui que escolhi o atraso entre duas notas adjacentes de ouvido. Acabou um pouco mais lento do que no original.

Conclusão


Concluindo, quero dizer que trabalhar com hardware às vezes acaba sendo mais interessante do que escrever um monte de código de alto nível.

Se alguém de repente quiser melhorar minha melodia ou escrever algo próprio, faça o seu comentário.

PS


Se você decidir tocar na dinâmica do sistema e não souber o básico da notação musical, então, no spoiler, poderá encontrar algumas dicas.

Dicas musicais
. .
.

All Articles