We play music from Mario on the system speaker

Mario  Notes

Foreword


Hello World!

Already about 3 years I want to write something on Habr, but there was no topic on which to post a post. That was until I needed to learn a little about the work of the system timer and system speaker for laboratory work. Having scoured a bit on the Internet, I did not find anything practical: something was written in too complicated a language, something was not particularly meaningful. I got a good book, all night long and tried to play the well-known theme from the Mario game. Continuation right under the cut, it seems like you have it here.

Disclaimer


The code is written as it is written. The author is not a programming genius, but just a student, but nevertheless, he tried to write the most readable and understandable code. Everything was written in Borland C and was tested in DOSBox only because there is no established dos and I don’t really want to screw up with a real-time clock.

Back in 2012Loiqig already wrote a cooler version , but, as it seemed to me, paid little attention to theory.

Also, the author (i.e., I) has 4 years of musical education and was poor in solfeggio (musical notation).

Bit of theory


A long time ago, when the Intel 8086 processor was popular, and the IBM PC did not raise any questions, Intel 8253 was used in these same IBM PCs and compatible computers - a timer and an interval counter. In modern computers, the south bridge deals with this ( Source: Wikipedia ).

Sample Intel 8253 PIT logic:

Logic diagram of Intel 8253. Fig.  1

Logic diagram of Intel 8253. Fig.  2

As you can see in the image above, the timer is connected to the IRQ0 line. It generates an 8h interrupt 18.2 times per second.

The timer consists of 3 counters (COUNTER0-2), which operate independently of each other.

No matter how strange it may be, each counter does its job. In modern computers, the first channel counts the time of day. Using the second channel, DRAM is regenerated. Using the third channel, you can make a pseudo-random number generator and shave it with a system speaker.

Each channel has 6 operating modes:

  • Mode 0 - terminal account interruption
  • Mode 1 - Programmable Standby Multivibrator
  • Mode 2 - Pulse Frequency Generator
  • Mode 3 - meander generator
  • Mode 4 - software-generated strobe
  • Mode 5 - hardware-based strobe

Let's get down to business


So, we learned a little theory about the system timer, learned that the third channel is connected to the system speaker. Everything seems to be cool. Just how to use this to play a theme from Mario? It’s not clear yet.

Each counter (channel) is programmed separately. We have already decided that you need to use the third. As you can see in the image above, the speaker is connected to the OUT output. At the same time, it is connected to port 61h, with which we can control the speaker. The first (least significant) bit is connected to Gate2 input and determines whether the counter is working or not. The second bit starts the speaker.

Based on this theory, the front of work for playing sound becomes clear:

  • We program CLOCK2 at the frequency we need (more on that later)
  • Use the first two bits of 61h to turn on the speaker

Since the default system speaker can play only one-voice sounds (well, as it was on the old push-button phones, where the boomer was played), we need to get the frequency of each note.

Table of ratios of notes and frequencies


, / , / 2.

In order to set the desired frequency for our counter, you need to use a certain formula: 1193182 / N Hz, where 1193182 is the timer frequency (1.193182 MHz if to be correct), N is the frequency of the note that you decided to output to the speaker.

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

The main function is terribly simple and, frankly, poorly optimized, but it does not change the essence of the matter.

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

So what about our mario?


We learned to play sounds through system dynamics. Fine! But how do we play a tune from Mario?

Using the magic of googling we find the notes:

Sheet music ringtones from Mario
.

Next, you will have to remember the course of musical notation and write out each note:

The written notes and their 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

It is worth noting that I introduced several assumptions. In a real melody, two notes are played simultaneously. I did not want to synchronize the two dosboxes, so I played on the same note.

Armed with even greater patience, we translate each note into frequencies and collect arrays of frequencies and durations:

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

In this melody, I counted 43 notes. It is also worth noting here that I chose the delay between two adjacent notes by ear. It turned out a little slower than in the original.

Conclusion


In conclusion, I want to say that working with hardware sometimes turns out to be more interesting than writing a bunch of high-level code.

If someone suddenly wants to improve my melody or write something of their own, then you are welcome to comment.

PS


If you decide that you want to play on the system dynamics and you do not know the basics of musical notation, then under the spoiler you can find a couple of hints.

Musical hints
. .
.

All Articles