【.NET 与树莓派】让喇叭播放音乐
如果你和老周一样,小时候特别喜欢搞破坏(什么电器都敢拆),那下面这样小喇叭你一定见过。
这种喇叭其实以前很多录音机都用,包括上小学时买来做英语听力的便携录音机。嗯,就是放录音带的那种,录音带也叫磁带或卡带,有两个轮子,录音机的动力转轴会带动轮子转动,然后就能听到声音了。
小时候,放学从学校走回家,途中就能看到不少于十处卖录音带的,有文具店的,有蹲路边卖的,甚至连一些卖早餐的店也卖。至于是否正版,这个你懂的。反正 1.5 元到 2 元一盒,零花钱省点用的话,一个星期能买到两三盒,然后回到家里又可以嗨了。也不用担心被长辈们发现,因为他们也喜欢听,被发现了他们会和你一起分享。
玩具用的喇叭会稍微比这个小一点,有正圆形的,也有椭圆形的;老周曾经在一个电子琴里拆出有正方形的喇叭,以老周幼年的拆机经验,方形是极罕见的,正圆形居多。
网上购买,一般会买到有焊接杜邦线的,以及无焊接线的。
有焊接杜邦线的就爽了,直接上线;至于无线的,如果你焊接技术好的话,也可以自己焊,如果没电烙铁没焊锡丝也不要紧,可以用带杜邦头的鳄鱼夹,直接夹住接线孔就行了。毕竟两边的线离得比较远,两个鳄鱼夹不会碰到一起,就不必担心短路了。
除了上面介绍的喇叭,还有一种模块也能播放音乐,那就是蜂鸣器。
注意蜂鸣分为无源和有源,上图中,左边的是无源蜂鸣器,右边的是有源蜂鸣器。
有源蜂鸣器,只要电平信号就能发声,而且只能产生固定音高的声音,所以,想让它播放音乐是没门的。从图中你会看到,有源蜂鸣器上那块黑色的圆柱体(像牛粪的那个)上贴着纸签,如果不撕掉,发出的声音刺耳但音量很小;如果把纸签撕掉,音量变大但没那么刺耳。我们这个让喇叭播放音乐的实验必须使用无源蜂鸣器,所以买的时候要看清楚是有源的还是无源的。
好了,上面介绍的是完成实验的器件,至于是选小喇叭还是蜂鸣器,你看着办,因为两者原理一样——我们物理课上学过,音高是由频率决定的。
上一篇烂文中,老周扯了 PWM 调光的实验,由于 PWM 能以不同的频率输出电平信号,所以设置不同的频率,再发送PWM方波,就能让喇叭发出不同音高的声音了。喇叭不能直接上电源,那样是不能放音乐的,只能听见地雷爆炸的声音。再次强调一下,是改变 PWM 的频率,不是占空比,改变占空比只能控制声音强度。
下面开始实验,此次实验老周选了一首很简单的歌,大家都听过的,《世上只有妈妈好》。简谱如下:
速度是每分钟 80 拍,所以每一拍(四分音符)的时长为 60/80 = 750 毫秒。接下来咱们确定一下曲中各音符的时值。
1、带附点的四分音符,时值为 750 + 750/2 = 1125 ms。附点就是延长当前音符时值的一半,所以四分音符加附点就是加上半拍的时值。
2、四分音符:750ms。
3、八分音符:750 / 2 = 375ms。
4、二分音符,后面有一横线的,就是两拍,750 * 2= 1500 ms。
至于每个音符的频率,可以直接网上查。
此处老周选用国际标准 A(中音 La)的频率(440 Hz)作为中音音域的参考点,于是得到各音阶的频率。
音阶 | 频率 | 最终取值 | |
低音部分 | 低音 5 | 195.998 | 196 Hz |
低音 6 | 220.0 | 220 Hz | |
低音 7 | 246.942 | 247 Hz | |
中音部分 | 中音 1 | 261.626 | 262 Hz |
中音 2 | 293.665 | 294 Hz | |
中音 3 | 329.628 | 330 Hz | |
中音 4 | 349.228 | 349 Hz | |
中音 5 | 391.995 | 392 Hz | |
中音 6 | 440.0 | 440 Hz | |
中音 7 | 493.883 | 494 Hz | |
高音部分 | 高音 1 | 523.251 | 523 Hz |
封装一个类,名为 NotePlayer,调用 PlayNote 方法播放指定频率的声音,持续 X 毫秒。
- class NotePlayer : IDisposable
- {
- private PwmChannel _pwmch = null;
- // 构造函数
- public NotePlayer() => _pwmch = PwmChannel.Create(0, 0);
- public void Dispose()
- {
- _pwmch?.Dispose();
- }
- /// <summary>
- /// 播放指定频率的声音
- /// </summary>
- /// <param name="freq">声音频率</param>
- /// <param name="duration">持续时间(毫秒)</param>
- public void PlayNote(int freq, int duration)
- {
- _pwmch.Frequency = freq;
- _pwmch.Start(); // 开始播放
- DelayHelper.DelayMillis(duration);
- _pwmch.Stop(); // 停止播放
- }
- }
核心部分是 PlayNote 方法,首先设置频率,然后调用 PwmChannel 的Start方法开始发送脉冲,随后持续一段时间(这段时间就是音符的时值,请看上文),播放完后,调用 Stop方法停止脉冲,喇叭不发声。
这里面有个辅助方法 DelayMillis,用来暂停 X 毫秒,你完全可以用 Thread.Sleep 方法,这里老周写这个方法,用的是另一种思路——这是参考微软的写法。
- class DelayHelper
- {
- public static void DelayMillis(int ms)
- {
- long ticks = ms * Stopwatch.Frequency / 1000;
- long targetTicks = Stopwatch.GetTimestamp() + ticks;
- do
- {
- Thread.SpinWait(1);
- }
- while (Stopwatch.GetTimestamp() < targetTicks);
- }
- }
原理是运用了 Stopwatch 类的计时器,GetTimestamp 方法总能返回计时器最新的 Tick,接着进入循环,每轮循环中调用 Thread.SpinWait(1) 只等待一个代码周期,这个时间很短,微秒级别的。循环退出条件是 GetTimestamp 方法返回的 Tick 达到我们预定好的时间。
这种方案适合对时间精度高的等待方案,比如等待几十微秒的。
这里要思考一件事:我们如果把每首曲子的音符都写进代码中,如果要播放其他曲子就得改一大遍代码,很不灵活。当然像 Arduino 那样没有操作系统且内部存储空间很小的板子,要么把代码写死,要么加个外部的 SD 卡模块,把音符信息放SD卡上,然后在代码中读。对于树莓派来说,这事情好办得要命。树莓派带操作系统,而且自身有 micro SD 卡接口,读写文件相当方便。
因此,老周把《世上只有妈妈好》的音符频率和时值输入到一个文本文件中,要换曲子直接换个文件就完事。格式很简单,每行一个音符,包括频率和时值,用空格分开。于是,《世上只有妈妈好》的文件如下:
- 440 1125
- 392 375
- 330 750
- 392 750
- 523 750
- 440 375
- 392 375
- 440 1500
- 0 750
- 330 750
- 392 375
- 440 375
- 392 750
- 330 375
- 294 375
- 262 375
- 220 375
- 392 375
- 330 375
- 294 1500
- 0 750
- 294 1125
- 330 375
- 392 750
- 392 375
- 440 375
- 330 1125
- 294 375
- 262 1500
- 0 750
- 392 1125
- 330 375
- 294 375
- 262 375
- 220 375
- 262 375
- 196 1500
- 0 750
其中,你会看到有几行,音符频率是 0,这个是为了让喇叭有停顿。
再写一个 MusicPlayer 类,可以控制播放整首曲子,并可以指定循环次数。
- public class MusicPlayer : IDisposable
- {
- private bool _playing = false; // 表示是否正在播放
- NotePlayer _noteplayer = null;
- Stream _stream = null;// 文件流
- #region 构造函数
- public MusicPlayer(string noteFilepath)
- {
- _noteplayer = new NotePlayer();
- _stream = File.OpenRead(noteFilepath);
- }
- #endregion
- /// <summary>
- /// 播放音乐
- /// </summary>
- /// <param name="count">重复次数,-1表示无限循环</param>
- public void Start(int count = 1)
- {
- _playing = true;
- if(count == -1) // 无限循环
- {
- while(_playing)
- {
- PlaySong();
- }
- }
- else
- {
- while (_playing && count > 0)
- {
- PlaySong();
- count--;
- }
- }
- }
- /// <summary>
- /// 停止播放
- /// </summary>
- public void Stop() => _playing = false;
- public void Dispose()
- {
- _stream?.Close();
- _stream?.Dispose();
- _noteplayer?.Dispose();
- }
- #region 私有方法
- private void PlaySong()
- {
- string line = null;
- _stream.Seek(0L, SeekOrigin.Begin);
- // 这里一定要让 leaveOpen 参数为 true
- // 不然 reader 关闭时会直接把文件给释放
- // 后面就不能播放第二遍了
- using StreamReader _noteReader = new(_stream, leaveOpen: true);
- line = _noteReader.ReadLine();
- int freq, dura;
- while (_playing && (line is not null))
- {
- string[] _s = line.Split(' ');
- if (!int.TryParse(_s[0].Trim(), out freq))
- {
- continue;
- }
- if (!int.TryParse(_s[1].Trim(), out dura))
- {
- continue;
- }
- if (freq < 0 || dura < 0)
- {
- continue;
- }
- // 播放音符
- _noteplayer.PlayNote(freq, dura);
- // 播放完读下一个音符
- line = _noteReader.ReadLine();
- }
- }
- #endregion
- }
打开包含音符频率和时值的文件,一行一行地读。每读出一行,以空格作分隔符拆开字符串——可拆成两个元素的字符串数组。第一个元素为频率,第二个元素为时值,随后用前面封装的 PlayNote 播放。
注意实例化 StreamReader 时,一定要保证它被释放时不要关闭文件,不然打开文件后只能播放一次了,后续的重复播放就会报错。
回到程序的 Main 方法。
- class Program
- {
- // 声明字段
- static MusicPlayer ply = null;
- static void Main(string[] args)
- {
- // 当按取消键时清理资源
- Console.CancelKeyPress += (_,_) =>
- {
- ply?.Stop();
- ply?.Dispose();
- };
- ply = new("./test01.txt");
- // 尝试通过命令行参数获取播放次数
- int count = -1;
- if(args is { Length: > 0})
- {
- string s = args[0];
- if(!int.TryParse(s,out count))
- {
- count = -1;
- }
- }
- Console.WriteLine($"播放{count}次……");
- ply.Start(count);
- ply.Dispose();
- }
- }
这里还实现了通过命令行参数来设定循环播放次数,-1为单曲循环。
最后是发布,上传到树莓派。
下面看怎么接线。
一、如果用小喇叭,注意正负极。如下图,左边是负极(接线孔右侧有“-”),右边是正极(接线孔左侧有“+”)。负极接树莓派的 GND(有多个,随便挑一个),正极串联一个大于 100 Ω 的电阻(电阻一定要接,不然会有破音,而且时间长了会烧掉喇叭,阻值 100 - 200 均可,电阻大了声音小一点)后接 GPIO 18,这个你看过上一篇文章就知道了,4B 只有这个引脚能产生第一路 PWM,其他树莓派你可以自己试。
二、使用无源蜂鸣器。这个得看你买的模块是什么样子的,老周买的这个是三个引脚的。
VCC 接树莓派供电脚,3.3V 和 5V 均可,都兼容,放心烧不了,上面有100欧的电阻。
GND 接树莓派 GND。
IO 接树莓派的 GPIO 18。
执行程序,就可以欣赏音乐了。
示例源代码,请点击这里
=====================================================================
补充一下,开板只能产生方波,不能产生正弦(含余弦)波,更不能产生叠加的交流声波。所以,它只能依据频率来产生不同的音高,你不能控制其音色,更别指望变成自制 Midi。树莓派主板上是有 3.5 mm 音频接口的,要看电影要听歌,跟电脑一样,插个耳机或有源音箱(如低音炮)即可。也可以去买一块专门的功放模块(针对像 Arduino 那样没有音频接口的板子),不用写代码驱动,插上音响就能嗨。当然也有蓝牙功放模块,网购无极限,啥都有可能买到。所以这年头想DIY还是比较容易的。
下一篇烂文,老周会说一下用 PWM 来驱动舵机,以及调节风扇的转速。
【.NET 与树莓派】让喇叭播放音乐的更多相关文章
- ESP32 LyraT音频开发板试玩(二):播放音乐
我是卓波,很高兴你来看我的博客. 系列文章: ESP32 LyraT音频开发板试玩(一):搭建开发环境 ESP32 LyraT音频开发板试玩(二):播放音乐 本文延续上一篇博客 将D:\msys32\ ...
- pcDuino-V2利用madplay播放音乐
在pcDuino的UBUNTU系统下,打开控制台,利用apt-get来下载madplay软件. sudo apt-get install madplay 播放音乐: madplay xxx.mp3 x ...
- matlab播放音乐
最近在做计算,写了一些matlab代码,脑壳还疼,所以决定发挥一下逗B精神,写一个程序玩一下. 想了想,既然写代码的时候喜欢听歌,而且我的电脑打开网易音乐的速度巨慢(不知道为什么..),那些一个程序直 ...
- go语言让windows发出声音,或者播放音乐
go语言让windows发出声音,或者播放音乐的例子:会发出alert警告的声音 ( 这是我应群员的求助写的, 如果你需要了解其中的调用原理或过程 或更多go语言调用win32api的资料,加群: 2 ...
- Inno调用dll --- 实现背景播放音乐
Inno 播放音乐可以调用系统api函数mciSendString来实现,而mciSendString函数存在C:\Windows\System32\winmm.dll中,因此最终只要成功从该dll文 ...
- 【分享】4412开发板ubuntu 12.0.4播放音乐没有声音解决方法
转自迅为论坛:http://bbs.topeetboard.com 准备工作 1.下载 vim 在命令行上输入 apt-get install vim 下载 vim 2.输入 vim /etc/hos ...
- 在Linux终端命令行下播放音乐的命令(Ubuntu)
现在的 Linux 桌面已经发展的很好了,在桌面下播放音乐操作起来也很简单.那么我们还记得在桌面不是那么好的时候我们是怎么播放音乐的么?哎,我是想不起来了,实在是太难了. 不过现在我们可以先安装一个小 ...
- C# 调用系统winmm.dll 播放音乐wav mp3
using System.Runtime.InteropServices;//放wav public partial class frmMain : Form { 系统放 播放音乐 wav mp3 [ ...
- iOS开发系列--扩展--播放音乐库中的音乐
众所周知音乐是iOS的重要组成播放,无论是iPod.iTouch.iPhone还是iPad都可以在iTunes购买音乐或添加本地音乐到音乐 库中同步到你的iOS设备.在MediaPlayer.fram ...
随机推荐
- 【MYSQL】win7安装mysql-5.7.10绿色版
1.下载 :mysql下载地址 2.解压缩 3.环境变量配置 MYSQL_HOME=D:\mysql-5.7.11-win32 PATH=%MYSQL_HOME%\bin 4.修改配置文件 a.)将m ...
- kubernets之secret资源
一 对于一些保密度比较高的文件,k8s又是如何存储的呢? 针对那些保密度比较高的配置文件,例如证书以及一些认证配置不能直接存储在configmap中,而是需要存储在另外一种资源中,需要对存储在里面的 ...
- [分享] 最流行的 10 个 JavaScript 库
1. Lodash https://github.com/lodash/lodash 一个工具库,用得还蛮多. 2. Chalk https://github.com/chalk/chalk 给终端加 ...
- mybatis源码解析之架构理解
mybatis是一个非常优秀的开源orm框架,在大型的互联网公司,基本上都会用到,而像程序员的圣地-阿里虽然用的是自己开发的一套框架,但其核心思想也无外乎这些,因此,去一些大型互联网公司面试的时候,总 ...
- 架构风格 vs. 架构模式 vs. 设计模式(译)
4.架构风格 vs. 架构模式 vs. 设计模式(译) - 简书 https://www.jianshu.com/p/d8dce27f279f
- pywin32 pywin32 docx文档转html页面 word doc docx 提取文字 图片 html 结构
https://blog.csdn.net/X21214054/article/details/78873338# python docx文档转html页面 - 程序猿tx - 博客园 https:/ ...
- LOJ10132
在 Adera 的异时空中有一张地图.这张地图上有 N 个点,有 N-1 条双向边把它们连通起来.起初地图上没有任何异象石,在接下来的 M 个时刻中,每个时刻会发生以下三种类型的事件之一: 地图的某个 ...
- 利用Javascript制作网页特效(其他常见特效)
设置为首页和加入收藏夹 ①:在body标签内输入以下代码: <a onclick="this.style.behavior='url(#default#homepage)'; this ...
- Java8中流的性能
流(Stream)是Java8为了实现最佳性能而引入的一个全新的概念.在过去的几年中,随着硬件的持续发展,编程方式已经发生了巨大的改变,程序的性能也随着并行处理.实时.云和其他一些编程方法的出现而得到 ...
- 使用 Shiro,从架构谈起,到框架集成!
使用 Shiro,从架构谈起,到框架集成! 一.架构 1.使用用户的登录信息创建令牌 2.执行登陆动作 3.判断用户 4.两条重要的英文 二.实现Realm 1.缓存机制 2.散列算法与加密算法 3. ...