<sub id="gqw76"><listing id="gqw76"></listing></sub>
      <sub id="gqw76"><listing id="gqw76"></listing></sub>

    1. <form id="gqw76"><legend id="gqw76"></legend></form>
    2. 【.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 來驅動舵機,以及調節風扇的轉速。

       

      posted @ 2021-02-10 12:54  東邪獨孤  閱讀(1716)  評論(5編輯  收藏
      最新chease0ldman老人|无码亚洲人妻下载|大香蕉在线看好吊妞视频这里有精品www|亚洲色情综合网

        <sub id="gqw76"><listing id="gqw76"></listing></sub>
        <sub id="gqw76"><listing id="gqw76"></listing></sub>

      1. <form id="gqw76"><legend id="gqw76"></legend></form>