我们在系统扬声器上播放Mario的音乐

马里奥  笔记

前言


你好,世界!

我已经约3年了,想在Habr上写点东西,但是没有关于发布主题的文章。直到我需要学习一些有关实验室工作的系统计时器和系统扬声器的工作。在Internet上进行了一些搜索之后,我发现没有什么实用的东西:有些东西是用太复杂的语言编写的,有些东西并不是特别有意义。我整夜整晚都有一本好书,并尝试玩Mario游戏中的著名主题。继续进行剪切,好像您在这里。

免责声明


代码是按编写的方式编写的。作者不是编程天才,而是一名学生,但尽管如此,他还是尝试编写了最易读和易懂的代码。一切都是用Borland C编写的,并且在DOSBox中进行了测试,只是因为没有确定的dos,而且我真的不想弄乱实时时钟。

早在2012年来奇 我已经写了一个很酷的版本,但是,在我看来,它几乎没有关注理论。

另外,作者(即I)接受了4年的音乐教育,并且在视唱练音(音乐符号)方面表现不佳。

一点理论


很久以前,当英特尔8086处理器流行并且IBM PC没提出任何问题时,在这些相同的IBM PC和兼容计算机中使用了Intel 8253-计时器和间隔计数器。在现代计算机中,南桥负责处理此问题(资料来源:Wikipedia)。

示例Intel 8253 PIT逻辑:

Intel 8253的逻辑图。 1个

Intel 8253的逻辑图。 2

如上图所示,计时器连接到IRQ0线。它每秒产生18.2次的8小时中断。

定时器包含3个计数器(COUNTER0-2),它们彼此独立工作。

不管它有多奇怪,每个柜台都在做自己的工作。在现代计算机中,第一个频道会计算一天中的时间。使用第二个通道,DRAM被重新生成。使用第三个通道,您可以制作一个伪随机数发生器,并用系统扬声器将其剃除。

每个通道有6种操作模式:

  • 模式0-终端帐户中断
  • 模式1-可编程待机多谐振荡器
  • 模式2-脉冲频率发生器
  • 模式3-曲流发生器
  • 模式4-软件产生的频闪
  • 模式5-基于硬件的频闪

让我们转到工作上


因此,我们了解了一些有关系统计时器的理论,并了解到第三声道已连接至系统扬声器。一切似乎都很酷。只是如何使用它来播放Mario的主题?目前尚不清楚。

每个计数器(通道)分别编程。我们已经决定您需要使用第三个。如上图所示,扬声器已连接到OUT输出。同时,它连接到端口61h,我们可以通过它控制扬声器。第一位(最低有效位)连接到Gate2输入,并确定计数器是否正在工作。第二位启动扬声器。

基于此理论,播放声音的工作前沿变得清晰:

  • 我们以所需的频率对CLOCK2进行编程(稍后会详细介绍)
  • 使用61h的前两位打开扬声器

由于默认的系统扬声器只能播放一声声音(就像在播放婴儿床的老式按键式电话上一样),我们需要获取每个音符的频率。

音符与频率的比率表


, / , / 2.

为了为我们的计数器设置所需的频率,您需要使用以下公式:1193182 / N Hz,其中1193182是计时器频率(如果正确则为1.193182 MHz),N是您决定输出到扬声器的音符的频率。

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

主要 功能非常简单,坦率地说,优化效果很差,但是它并没有改变问题的实质。

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

那我们的马里奥呢?


我们学会了通过系统动力学来播放声音。精细!但是我们如何演奏马里奥的曲调?

使用谷歌搜索的魔力,我们发现了注释:

马里奥的乐谱铃声
.

接下来,您将必须记住乐谱的过程并写下每个音符:

书面说明及其尺寸:
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

值得注意的是,我介绍了几个假设。在真实的旋律中,同时演奏两个音符。我不想同步两个Dosbox,所以我演奏相同的音符。

有了更大的耐心,我们将每个音符转换为频率并收集频率和持续时间的数组:

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

在这首旋律中,我数了43个音符。在这里还值得注意的是,我选择了两个相邻音符之间的延迟时间。事实证明,它比原始版本慢一些。

结论


总之,我想说的是,有时与编写一些高级代码相比,使用硬件工作更有趣。

如果有人突然想提高我的旋律或自己写一些东西,那么欢迎您发表评论。

聚苯乙烯


如果您决定要在系统动力学上演奏,并且您不了解乐谱的基础知识,那么在扰流板下您可以找到一些提示。

音乐提示
. .
.

All Articles