如果你和老周一样,小时候特别喜欢搞破坏(什么电器都敢拆),那下面这样小喇叭你一定见过。
这种喇叭其实以前很多录音机都用,包括上小学时买来做英语听力的便携录音机。嗯,就是放录音带的那种,录音带也叫磁带或卡带,有两个轮子,录音机的动力转轴会带动轮子转动,然后就能听到声音了。
小时候,放学从学校走回家,途中就能看到不少于十处卖录音带的,有文具店的,有蹲路边卖的,甚至连一些卖早餐的店也卖。至于是否正版,这个你懂的。反正 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)作为中音音域的参考点,于是得到各音阶的频率。
音阶频率最终取值低音部分低音 5195.998196 Hz低音 6220.0220 Hz低音 7246.942247 Hz中音部分中音 1261.626262 Hz中音 2293.665294 Hz中音 3329.628330 Hz中音 4 349.228 349 Hz 中音 5 391.995 392 Hz 中音 6440.0440 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 1125392 375330 750392 750523 750440 375392 375440 15000 750330 750392 375440 375392 750330 375294 375262 375220 375392 375330 375294 15000 750294 1125330 375392 750392 375440 375330 1125294 375262 15000 750392 1125330 375294 375262 375220 375262 375196 15000 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(#34;播放{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还是比较容易的。
原文地址:https://www.cnblogs.com/tcjiaan/p/14395271.html
转载请注明来自广州玛斯顿影音有限公司,本文标题:《「.NET 与树莓派」让喇叭播放音乐 》