搜索
bottom↓
回复: 4

请教:无源蜂鸣器铃声是怎么获取的(音调和对应时长)

[复制链接]

出590入1001汤圆

发表于 2021-8-8 21:34:28 | 显示全部楼层 |阅读模式
这两天研究了:amigenius 大神的:开源伪和弦淫乐声的产生原理及电路,源码
https://www.amobbs.com/thread-5724546-1-1.html
二楼会整理:自己的处理的代码放在二楼,里面有自己研究非常详细的标注。

但是其中有一个问题:
假如现在有一段音乐(比如两只老虎,生日歌),怎么样取出其中的音调和时间?用什么软件比较方便,或者用乐谱吗?

比如帖子中的铃声(超级玛丽的1UP声音)用什么方法获取比较好?:
const uint16 Music7_FrqTab[]={
        1324,1324,1574,1574,2645,2645,        2114,2114,2347,2347,3154,3154,        0,
};
const uint8 Music7_TimeTab[]={                //x4ms
        15,   25,  15,  25,  15,   25, 15,  25  , 15 ,  25  , 15,  255, 0,
};

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出590入1001汤圆

 楼主| 发表于 2021-8-8 21:34:49 | 显示全部楼层
本帖最后由 SUPER_CRJ 于 2021-8-8 21:39 编辑

二楼,占楼,自己修改的代码。

beepMusic.c文件如下:
  1. #include "beepMusic.h"

  2. //触发发声标志,这不是布尔量,而是选择歌曲的曲目数字。
  3. //为什么叫:triger,因为选择曲目后,就会变为0。就是一个triger一样。
  4. u8 Music_Vol=137;    //音量
  5. vu8  Music_Triger=0;   //触发播放哪一支,例如播放“叮~~~”,则任意地方调用 Music_Triger=MUSIC_DING;
  6. u8  flg_MusicPlaying=0;   //非零播放中。

  7. //乐曲表指针
  8. // 下面指针表示,指向的数据内容不变!
  9. u16 const*  music_frq_tab;      
  10. u8  const*  music_interval_tab;

  11. //定义不同的乐曲数组,0 结束
  12. //频率表,对应BUZ-H的输出频率,0 结束
  13. const u16 Music1_FrqTab[]={_18,_18,_21,_21,_24,_24,_28,_28,0,};
  14. //BUZ-L输出时间,偶数高电平,奇数为低电平尾音,x4ms,0 结束
  15. // 时间上要乘4ms,为什么是4ms?为什么不是其他数字?
  16. // 当然:关于时间中断上,最好是时间越大越好。减少中断的次数。
  17. // 当然:1ms的时间中断根据μc/OS-Ⅲ和一些经常需要1ms调用上,时间上是可以接受的。
  18. // 发现也有用10ms的,beep的时间规范可能不是那么明显。一般音调都是有时间,人是取值不同而已。
  19. // 猜测应该是这个公约数好约?当然人对于时间的敏感显然比较好处理?不够敏感!
  20. // 还有个问题:关于时间,为什么最后一个的时间都是:255(255*4=1020ms,这应该是准备最后一个不断降低的“伪和弦”)
  21. // 查看了些文档最后一个音调都比较长,应该是需要最后一个有慢慢降低的感觉。
  22. const u8 Music1_TimeTab[]={ 20,30,20,30,20,30,20,255,0,};

  23. const u16 Music2_FrqTab[] = {_21,_21,_22,_22,_24,_24,0};
  24. const u8 Music2_TimeTab[] = {10,18,10,18,10,255,0,};  

  25. const  u16 Music3_FrqTab[]={_24,_24,0,};
  26. const  u8 Music3_TimeTab[] ={ 18,255,0,};

  27. const  u16 Music4_FrqTab[] ={_28,_28,_24,_24,_21,_21,0};
  28. const  u8 Music4_TimeTab[]={ 10,18,10,18,10,255,0,};

  29. const  u16 Music5_FrqTab[] ={_16,_16,_18,_18,_21,_21,0};
  30. const  u8 Music5_TimeTab[] ={6,16,6,16,6,255,0,};

  31. const  u16 Music6_FrqTab[] ={_21,_21,_18,_18,_16,_16,0};
  32. const  u8 Music6_TimeTab[] ={6,16,6,16,6,255,0,};

  33. // 这是超级玛丽的1UP音乐。
  34. // 可以看出,以前的游戏音乐存储是真的简单和少。
  35. const u16 Music7_FrqTab[]={1324,1324,1574,1574,2645,2645,2114,2114,2347,2347,3154,3154,0,};
  36. const u8 Music7_TimeTab[]={15,25,15,25,15,25,15,25,15,25,15,255,0,};



  37. /**
  38. * \b 设置音量,根据分析,其音量大小为占空比的大小。具体逻辑要进行分析!
  39. *    分析到:应该到一定程度没有到0就应该不行了。大到一定程度就也应该声音就模糊了。
  40.                         下面的代码就应该要具体分析了。
  41.                         突然想到以后:是不是应该要做低功耗应用!蜂鸣器其实挺浪费电的。

  42.                 其实这个程序能用,但是相对也比较混乱,单纯的配置声音可以,放入了一些其他过多的东西!
  43. */
  44. void Music_SetVolumn(u8 vol)
  45. {     
  46.         u32 lTmp;
  47.         lTmp=TIM3->ARR.AutoReloadValue_RW;
  48.         lTmp*=vol;
  49.         lTmp/=255;
  50.         TIM3->CCR3.CaptureCompareValue_RW = lTmp;  // 设置到这里就可以了!
  51. }

  52. /**
  53. * \b 改变频率,务必导出其中的算法,中间还带有音量
  54.                         做音乐的一个比较重要的应该是计算出任意频率了
  55.                         之前一直没有做任意频率的,这次应该做了。
  56.        
  57.         实际计算方法:由于定时器设计关于,假设已知:定时器入口频率:tFre(HZ),需要生成的频率:pwmFre,占空比大小:duty(0-255大小)
  58.         需要计算:ARR值与CCR值。
  59.         根据下面两个计算pwm周期的两个公式。
  60.         Tpwm = 1/pwmFre
  61.         Tpwm = (ARR+1)*(1/tFre)
  62.         最终得到:ARR = ( tFre/pwmFre ) - 1;
  63.         而占空比(注意有效电平,这里假设有效电平:1):
  64.         CCR = duty/255*ARR值
  65.        
  66.         经常计算后:下面的设计满足要求。(当然,实际中要注意计算只能保留整数,但是:蜂鸣器控制的音乐,其精度是完全足够的!)

  67.         一个重要性能:使用固定的频率输入,其生成的PWM频率范围是多少
  68.         pwmFre = tFre/(ARR+1) 其中ARR范围:1-0xFFFF (0不应该被纳入其中)
  69.         当:tFre为7.2Mhz时:范围:109HZ - 3.6MHZ,显然这个范围下,时钟是完全够用的。

  70.         注意:里面同时改变了频率和声音的大小!
  71. */
  72. void Music_ChangeFrq(u16 frq)
  73. { //改变频率,与音量
  74.         u32 clk;
  75.         clk=7200000; // 7.2M hz的输出频率,就用这个时钟进行计算
  76.         clk/=frq; //
  77.         clk--;
  78.         /* Time base configuration */
  79.         TIM3->ARR.AutoReloadValue_RW = clk; // 这里应该可以导出频率
  80.         clk*=Music_Vol;
  81.         clk/=255;
  82.         TIM3->CCR3.CaptureCompareValue_RW = clk;
  83. }

  84. // 之前使用的是PA0,TIM2_CH1-ETR引脚(看来这个CH1-ETR引脚也可以使用PWM输出!)
  85. void Music_Frq_Enable(void){
  86.         TIM3->CCER.CaptureCompareOutputEnable3_RW = 1; // 使能捕获比较通道输出
  87. }

  88. void Music_Frq_Diable(void){
  89.         TIM3->CCER.CaptureCompareOutputEnable3_RW = 0; // 关闭输出
  90. }

  91. /**
  92. * \b IO口引脚初始化、定时器初始化
  93.                         使用了:PB0,TIM3-CH3
  94. */
  95. void Music_Init(void)
  96. {
  97.         { // PB0,TIM3-CH3引脚初始化
  98.                 RCC->APB2ENR.IOPBClockEnable_RW = ENABLE;
  99.                 GPIOB->CRL.InOutMode0_RW = GPIO_CR_INOUNTMODE_OUTPUT_50MHZ;
  100.                 GPIOB->CRL.PinConfig0_RW = GPIO_CR_PINCONFG_OUT_ALTERNATE_PUSHPULL;
  101.                 { // 供电控制引脚,此处没有对应的硬件电路,这里保持为空,为了日后方便升级这里结束
  102.                 }
  103.                 BEEPL_1;
  104.         }
  105.         { // 定时器初始化
  106.                 RCC->APB1ENR.TIM3ClockEnable_RW = 1;// 36MHZ-APB1时钟,定时器倍频到72MHZ
  107.                 TIM3->PSC.PrescaleValue_RW = 0x09; // 产生7.2M给定时器使用
  108.                 TIM3->CR1.UpdateRequestSource_RW = 0;
  109.                 TIM3->CR1.AutoReloadPreloadEnable_RW = 1; // 需要带缓冲
  110.                 {// 公用部分
  111.                         // 频率表:越大频率越小,最后使用
  112.                         TIM3->ARR.AutoReloadValue_RW = 0xFFFF; // 这里给了最大值
  113.                         TIM3->CR1.Direction_RW = 0; // 向上计数模式(默认值)
  114.                         TIM3->CR1.Direction_RW = 0; //向上计数
  115.                         TIM3->CR2.All = 0; // 默认值
  116.                         TIM3->SMCR.All = 0; // 默认值
  117.                         TIM3->DIER.All = 0; // 默认值
  118.                 }
  119.                 { //
  120.                         TIM3->Output_CCMR2.OutputCompareMode3_RW = _0b0111; // 巧了,都是3
  121.                         TIM3->Output_CCMR2.OutputComparePreloadEnable3_RW = 1;
  122.                         TIM3->CCER.CaptureCompareOutputPolarity3_RW = 1;   // 高电平有效果
  123.                         TIM3->CCER.CaptureCompareOutputEnable3_RW = 0;     // 关闭输出
  124.                         TIM3->CCR3.CaptureCompareValue_RW = 0xFFFF; // 这里也给了最大值
  125.                 }
  126.                 // 使能一次
  127.                 TIM3->EGR.UpdateGeneration_W = 1; //
  128.                 TIM3->CR1.CountEnable_RW = 1;
  129.         }
  130.        
  131.         // 当没有使能时钟的时候,这个设置没有意义。
  132.         Music_ChangeFrq(4000); // 为什么设置4000HZ的频率!
  133.         Music_Frq_Diable();
  134. }

  135. void Music_Select(void)
  136. {
  137.         switch(Music_Triger){
  138.                         case MUSIC_PWR_UP:
  139.                                 music_frq_tab=Music1_FrqTab;
  140.                                 music_interval_tab=Music1_TimeTab;
  141.                                 break;
  142.                         case MUSIC_TURN_ON:
  143.                                 music_frq_tab=Music2_FrqTab;
  144.                                 music_interval_tab=Music2_TimeTab;
  145.                                 break;
  146.                         case MUSIC_DING:
  147.                                 music_frq_tab=Music3_FrqTab;
  148.                                 music_interval_tab=Music3_TimeTab;
  149.                                 break;
  150.                         case MUSIC_TURN_OFF:
  151.                                 music_frq_tab=Music4_FrqTab;
  152.                                 music_interval_tab=Music4_TimeTab;
  153.                                 break;
  154.                         case MUSIC_UP:
  155.                                 music_frq_tab=Music5_FrqTab;
  156.                                 music_interval_tab=Music5_TimeTab;
  157.                                 break;
  158.                         case MUSIC_DOWN:
  159.                                 music_frq_tab=Music6_FrqTab;
  160.                                 music_interval_tab=Music6_TimeTab;
  161.                                 break;
  162.                         case MUSIC_1UP:
  163.                                 music_frq_tab=Music7_FrqTab;
  164.                                 music_interval_tab=Music7_TimeTab;
  165.                                 break;
  166.                         default:// 发现楼主喜欢:把default放到最后   
  167.                                 flg_MusicPlaying=0;
  168.                                 return;
  169.         }
  170.         flg_MusicPlaying=1;
  171. }

  172. /**
  173. * \b 核心程序,每4ms调用一次
  174.                         如果被不及时调用会使声音被拉长。4ms的一些应用不算什么。但是也比较占用了时间了!
  175.                         这里就是整个函数的处理核心了。
  176.                         由于代码都是我自己开发,自己使用,所以第个拿来的模块,都应该了解其中的原理,小心使用。完全的懂了,才方便使用!
  177.                         根据分析:里面没有阻塞操作,时间运行比较快,可以放在中断中处理!
  178. */
  179. void Music_Srv(void)
  180. {
  181.         static u8 PlayStep=0; // 这个用来指示频率运行到哪个步骤。
  182.         static u8 PlayCt=0;   // 用于计数播放多长时间。
  183.         u8 cTmp;

  184.         if(Music_Triger){
  185.                         Music_Select(); // 选择音乐
  186.                         Music_Triger=0; // 关闭扳机
  187.                         PlayStep=0;  // 清零计数
  188.                         PlayCt=0;    // 清零计数
  189.                  
  190.                         Music_ChangeFrq(music_frq_tab[PlayStep]); // 这里就是要求快速变换频率。
  191.                         Music_Frq_Enable(); // 使能输出

  192.                         BEEPL_1; // 开启蜂鸣器电源,就等于输出声音了。
  193.         }
  194.         if(flg_MusicPlaying){ // 当triger之后,这个就使能了。
  195.                 cTmp=music_interval_tab[PlayStep]; // 获取对应播放的时间。
  196.                 PlayCt++;
  197.                 if(PlayCt>=cTmp){ // 当对应的时间到达后,进行切换
  198.                         PlayCt=0;       // 计数清零
  199.                         PlayStep++;     // 切换到下一个标签
  200.                         Music_ChangeFrq(music_frq_tab[PlayStep]); // 改变频率
  201.                         if(music_interval_tab[PlayStep]==0){ // 遇到0表示结束。
  202.                                 flg_MusicPlaying=0;      
  203.                         }
  204.                         else{ // 可以理解为:0/1的来回切换。用于产生渐低音!
  205.                                 BEEPL_XOR;
  206.                         }
  207.                 }
  208.         }
  209.         else{ // 否则认为不在播放
  210.                 BEEPL_0;
  211.                 Music_Frq_Diable();
  212.         }
  213. }

复制代码


beepMusic.h 文件

  1. /**
  2. * @brief F1 定时器音乐
  3. * @Author
  4. */

  5. #ifndef __BEEP_MUSIC__
  6. #define __BEEP_MUSIC__

  7. #include "stm32f10x.h"
  8. #include "string.h"

  9. //BUZ-L定义IO
  10. //#define BEEPL_1 GPIOA_DSRR=BIT1
  11. //#define BEEPL_0 GPIOA_DRR=BIT1
  12. //#define BEEPL_XOR {if(BitTst(GPIOA_ODR,BIT1)) BEEPL_0; else BEEPL_1;}

  13. // 由于没有启用电源电容控制,这些设置为空,同时为了保留设计,此处保留!
  14. #define BEEPL_1 (0)   // 使能蜂鸣器供电
  15. #define BEEPL_0 (0)   // 关闭蜂鸣器供电
  16. #define BEEPL_XOR (0) //

  17. //音乐声定义
  18. #define MUSIC_PWR_UP 1
  19. #define MUSIC_TURN_ON 2
  20. #define MUSIC_DING 3
  21. #define MUSIC_TURN_OFF 4
  22. #define MUSIC_UP 5
  23. #define MUSIC_DOWN 6
  24. #define MUSIC_1UP 7

  25. // 定义常用频率,数字多少就是多少Hz
  26. // 这些频率到底代表着什么?
  27. // 为什么是这几个,为什么是6个。
  28. // 最后分析:原来是音乐中的“度”,一般都是8度。
  29. // 实际应用中应该根据不同蜂鸣器进行测试改变频度,这里面用了6度,其实也可以听出来一定的效果。
  30. // 所以用这几个不用太纠结。
  31. // 具体到音乐的深层次也没有必要的。
  32. #define _28 2850   
  33. #define _24 2400
  34. #define _22 2250
  35. #define _21 2100
  36. #define _18 1850
  37. #define _16 1650


  38. // 定义duo,rui,mi,fa,so等等
  39. // 下面好像是节拍,但是最后一个又不满足。所以暂时先不管。
  40. // 根据最新分析:不是节拍的时间,是“度”。应该是高中低音之类!
  41. // 注意:下面前面字符是“l”不是“1” (小写的L和1),这个定义比较差!
  42. #define _l1 130
  43. #define _l2 146
  44. #define _l3 164
  45. #define _l4 174
  46. #define _l5 196
  47. #define _l6 220
  48. #define _l7 246

  49. #define _1 261
  50. #define _2 293
  51. #define _3 329
  52. #define _4 349
  53. #define _5 392
  54. #define _6 440
  55. #define _7 494

  56. #define _1d1 523
  57. #define _2d1 587
  58. #define _3d1 659
  59. #define _4d1 698
  60. #define _5d1 784
  61. #define _6d1 880
  62. #define _7d1 987

  63. #define _1d2 1046
  64. #define _2d2 1175
  65. #define _3d2 1318
  66. #define _4d2 1397
  67. #define _5d2 1568
  68. #define _6d2 1760
  69. #define _7d2 1976

  70. #define _1d3 (_1d2*2)
  71. #define _2d3 (_2d2*2)
  72. #define _3d3 (_3d2*2)
  73. #define _4d3 (_4d2*2)
  74. #define _5d3 (_5d2*2)
  75. #define _6d3 (_6d2*2)
  76. #define _7d3 (_7d2*2)

  77. extern vu8 Music_Triger;     //触发发声标志
  78. extern u8  flg_MusicPlaying; //非0表示正在发声

  79. void Music_Srv(void);     //4ms调用一次,为什么是4ms调用一次?
  80. void Music_Init(void);    //初始化Music模块。
  81. void Music_Select(void);
  82. void Music_SetVolumn(u8 vol); // 可以单独的设置声音强度

  83. #endif
复制代码

出0入8汤圆

发表于 2021-8-8 23:30:10 来自手机 | 显示全部楼层
收藏了,谢谢分享

出200入2554汤圆

发表于 2021-8-8 23:55:17 来自手机 | 显示全部楼层
LZ需要些乐理知识、midi知识,就可以玩的更顺畅。

就拿音高来说,乐理里边CDEFGAB,对应从do到si,后边加一个数字0-7代表八度位置,于是就有C4、D5、E3这样的写法。
每个音高的基波频率固定,一般规定A4=440Hz,其余音高频率按照十二平均律划分,对应下来一个八度频率翻倍。

当然这是键盘乐、midi的规范,你要是玩小提琴拉出了 C4 和 #C4 之间的锯木头音高,那就另当别论了

出0入0汤圆

发表于 2021-8-9 06:30:40 来自手机 | 显示全部楼层
楼主厉害了,感谢分享
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-8-16 12:22

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表