用STEP Pico实现midi音乐播放
在STEP Pico上,对应midi音符的解析,控制蜂鸣器播放对应的声音,实现midi音乐的播放,及按键控制
标签
PICO
Python
WS2812B
2023寒假在家练
midi
HonestQiao
更新2023-03-29
648

项目介绍:

本项目在STEP Pico上,通过对应midi音符的解析,控制蜂鸣器播放对应的声音,实现midi音乐的播放,同时根据当前播放的音符,点亮对应数量和颜色WS2812B灯珠。

播放过程中,如果按K1按键,则暂停播放;如果按K2按键,则继续播放。

 

设计思路:

要实现本项目,需要做到四点:

  1. 音符的解析
  2. 音符的播放
  3. WS2812B灯珠的控制
  4. 按键状态的获取

通过了解,midi音符可以转换为频率和时长进行,通过蜂鸣器发生呈现。

而每个音符,对应了不同的数字,根据数字,选择对应的颜色,来点亮对应数量的WS2812B灯珠,从而实现项目预设的效果。

音符与频率的对照关系,参考下图:

国际标准音高包含了哆啦咪法嗦来嘻(表中用1234567)的多个音高的频率,为了尽可能的还原音乐,这里模拟了其中9个频率的音高,下表为音高对应的频率:

Fg19Dbm2pIQl28xuEot9GWn1C5we

在实际使用中,只需要将midi音符通过查表,找到对应的频率,再驱动蜂鸣器,播放指定的时长即可。

而每个音符,本身对应了1-7,通过1-7,可以选择不同的预设颜色,来驱动WS2812B灯珠进行显示。

而按键状态的获取,则直接通过数字IO读取按键状态即可。

 

硬件介绍:

本项目使用了Step pico及其对应的扩展板。在该扩展板上,提供了12灯珠的WS2812B,以及一个蜂鸣器。

根据官方提供的功能及管脚映射表,可以得到控制WS2812B和蜂鸣器的GPIO:

 

从上图可知:

  1. WS2812B对应GPIO18
  2. 蜂鸣器对应GPIO19
  3. K1对应GPIO12
  4. K2对应GPIO13

因为扩展板提供了上述外设完整的连接和控制线路,所以无需其他辅助,仅使用STEP pico+扩展板就可以完成全部工作。

 

实现功能:

STEP pico为RP2040核心,可以使用SDK开发,也可以使用Arduino开发,还可以使用micropython和circuitpython进行开发。本项目使用circuitpython进行开发。

通过circuitpython,最终实现的功能如下:

  1. 根据midi音乐的音符数据,驱动蜂鸣器进行播放
  2. 播放的同时,点亮指定颜色的WS2812B灯珠,实现灯珠跟随音乐的效果
  3. 播放时,按K1,则暂停播放
  4. 暂停播放时,按K2,则继续播放

 

代码说明:

最终编写的代码如下:

import board
import simpleio
import digitalio
import neopixel
import time

# 按键IO设置
k1 = digitalio.DigitalInOut(board.GP12)
k1.direction = digitalio.Direction.INPUT
k1.pull = digitalio.Pull.UP

k2 = digitalio.DigitalInOut(board.GP13)
k2.direction = digitalio.Direction.INPUT
k2.pull = digitalio.Pull.UP

# WS2812B设置
pixel_pin = board.GP18
num_pixels = 12

pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)

colors = (RED, YELLOW, GREEN, CYAN, BLUE, PURPLE)

def ws2812b_set(color, num, wait):
    for i in range(num_pixels):
        if i < num:
            pixels[i] = color
        else:
            pixels[i] = (0,0,0)
        if wait>0:
            time.sleep(wait)
        pixels.show()
    # time.sleep(0.5)


# 蜂鸣器设置
PIEZO_PIN = board.GP19
#simpleio.tone(PIEZO_PIN, 440, duration=0.1)

# 音符频率
pitch_freqs = (
    #  1	  2		  3		  4		  5		  6		  7
    (16.4, 	18.4,	20.6,	21.8,	24.5,	27.5,	30.9	),	# 0
    (32.7, 	36.7,	41.2,	43.7,	49.0,	55.0,	61.7	),	# 1
    (65.4, 	73.4,	82.4,	87.3,	98.0,	110,	123.5	),	# 2
    (130.8, 146.8,	164.8,	174.6,	196.0,	220,	246.9	),	# 3
    (261.6, 293.7,	329.6,	349.2,	392.0,	440,	493.9	),	# 4
    (523.3, 587.3,	659.3,	698.5,	784.0,	880,	987.8	),	# 5
    (1046.5,1174.7,	1318.5,	1396.9,	1568.0,	1760,	1975.6	),	# 6
    (2093.1,2349.4,	2637.1,	2793.9,	3136.0,	3520.1,	3951.2	),	# 7
    (4186.1,4698.8,	5274.2,	5587.8,	6272.1,	7040.2,	7902.3	)	# 8
)

# 要播放的音乐
music=(
24,89,
4,5,12,4,5,6,5,5,6,
7,5,36,5,5,12,4,5,12,
5,5,6,7,5,6,5,5,12,
4,5,12,2,5,72,2,5,12,
2,5,6,4,5,6,5,5,36,
7,5,12,4,5,12,2,6,12,
2,6,6,1,6,6,7,5,12,
1,6,72,7,5,12,1,6,12,
2,6,12,2,6,12,1,6,12,
2,6,6,1,6,6,7,5,36,
5,5,6,7,5,6,1,6,12,
1,6,12,1,6,6,7,5,6,
0
)

# 音乐基础信息获取
i = 2
km = music[0]				#//获取divisions值
beats_per_min = music[1]	#//获取曲子每分钟拍子数
pitch_quarter_time = int(1000*((60/beats_per_min)/4)+0.5)	#//根据每分钟节拍数,求解四分之一音符保持时长,四舍五入单位为1ms 
pitch_quarter_time = int(1000*(60/beats_per_min/km))
print(km, beats_per_min, pitch_quarter_time)

is_stop = False
while True:
    # 检查按键状态
    if not k1.value and not is_stop:
        print("press k1")
        is_stop = True
        
    if not k2.value and is_stop:
        print("press k1")
        is_stop = False        

    if is_stop:
        continue
        
    # 音符信息获取
    m1 = music[i+1] 	#//获取音符对应计数值所在行下标
    m2 = music[i]-1 	#//获取音符对应计数值所在列下标
    n = music[i+2] 		#//获取音符保持时长值

    # 点亮WS2812B
    ws2812b_set(colors[music[i]%6], music[i]%12, 0)


    # 音符播放
    freq = pitch_freqs[m1][m2]
    dt = pitch_quarter_time * n
    print(m1, m2, n, freq, dt)
    simpleio.tone(PIEZO_PIN, freq, duration=dt/1000)
    i = i+3  #//读下一个音符
    
    if music[i]==0:
        i=2	   #//如果到末尾则回到开头,循环播放
        # break

 

在上述代码中,使用的库说明如下:

  1. board:circuitpython自带的板子驱动库
  2. simpleio:驱动GPIO的功能库,这里使用其驱动蜂鸣器
  3. digitalio:数字IO库,这里使用其获取按键状态
  4. neopixel:WS2812B控制库
  5. time:python时间库

 

代码中:

1. 获取按键状态的部分较为简单,通说初始化按键对应的数字IO口,然后获取其状态即可。需要注意的是,默认为高电平,按下为低电平,所以检测的时候,使用了not k1.valuenot k2.value来进行。

2. WS2812B控制的部分,主要为:

  1. 初始化:pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
  2. 点亮指定的灯珠:ws2812b_set(colors[music[i]%6], music[i]%12, 0)

初始化部分,需要指定好GPIO以及总的灯珠数量。

点亮部分,实际上是给对应的灯珠,设置一个(R, G, B)的颜色值即可。

 

3. 音符播放部分,主要为:

  1. 给出了音符频率表:pitch_freqs
  2. 给出了要播放音乐的数据表
  3. 实际播放时:
    1. 通过音乐数据表,计算当前要播放的音乐的节拍信息,以及每个节拍对应的时间
    2. 通过音乐数据表,查找当前要播放的音符在音符频率表中对应的频率值
    3. 然后使用simpleio.tone(PIEZO_PIN, freq, duration=dt/1000),用对应的频率来驱动蜂鸣器鸣叫指定的时间

 

操作步骤:

  1. 通过thonny安装circuitpython:
    1. FhdULnSLgAxw_qRoO38lwq1GRmil
  2. thonny连接pico:
    1. FlecbTFDbTxERA2eRKs2HMcMlv8I
  3.  下载扩展库:
    1. Libraries (circuitpython.org):https://circuitpython.org/libraries
  4. 拷贝扩展库:
    1. FixNg-xXZz-2_fFjT84djGiqAnE9
  5. 代码编写:
    1. FliTqJYHMiEUpz3_MJ__h70B0c7P
  6. 运行代码,就能播放音乐,点亮WS2812B,然后可以按键进行控制
    1. FkSVJOBwxDAqEG8dZrWKiGwuIKFY

 

 

 

附件下载
代码提交.zip
Pico项目代码
团队介绍
一个狂热的开源爱好者和传播者,同时也是一名极客爱好者,长期关注嵌入式发展和少儿创客教育,既擅长互联网系统架构设计与研发,又拥有丰富的嵌入式研发经验。为人精力充沛,古道热肠,圈内人称乔大妈、乔帮主。
团队成员
HonestQiao
狂热的开源爱好者和传播者
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号