这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【Let'sdo2025第三期活动】【拾色播放器DIY】蜂鸣器音乐播放学习笔记

共1条 1/1 1 跳转至

【Let'sdo2025第三期活动】【拾色播放器DIY】蜂鸣器音乐播放学习笔记

菜鸟
2025-12-28 00:16:59     打赏

前言

这是"拾颜播放器"项目学习记录的第一部分,用蜂鸣器,播放不同风格的音乐。

以前也玩过蜂鸣器,基本用法知道,比如小星星。但这次系统整理了一下,发现有很多细节之前没注意到,比如音符频率其实可以计算、不同音阶有不同情绪、和弦怎么编配等。这篇笔记把学习过程中觉得有用的内容都记录下来。

一、硬件准备

项目用的硬件:

组件说明



ESP32-S3 Reverse TFT Feather主控板,240MHz 双核
DFRobot DFR0032数字蜂鸣器模块
TCS3200 颜色传感器检测颜色用
Littelfuse 59001接近传感器,检测物体靠近

引脚连接

BUZZER_PIN = board.D5

36fc0a4cb93662b92172d756a9ffd3e5.jpg

image.png

二、让蜂鸣器响起来

最开始的代码很简单:

import time
import board
import pwmio

buzzer = pwmio.PWMOut(board.D5, duty_cycle=0, frequency=440)
buzzer.duty_cycle = 32768  # 50% 音量
time.sleep(0.5)
buzzer.duty_cycle = 0      # 关闭

注意:duty_cycle 范围是 0-65535,32768 就是 50% 音量。

三、认识音符频率3.1 频率计算公式

其实不用死记硬背所有频率,记住一个公式就行:

频率 = 440 × 2^((n-69)/12)

其中 n 是 MIDI 音符编号。中央 C (C4) 的编号是 60。

def midi_to_freq(midi_note):
    """MIDI音符编号转频率"""
    return 440 * (2 ** ((midi_note - 69) / 12))# 计算几个常用音
    
print(f"C4 (60): {midi_to_freq(60):.2f} Hz")  # 261.63 Hz
print(f"A4 (69): {midi_to_freq(69):.2f} Hz")  # 440.00 H
zprint(f"C5 (72): {midi_to_freq(72):.2f} Hz")  # 523.25 Hz

3.2 常用频率表

下面是 test_buzzer.py 里直接用到的频率,整理成了表格:

音符频率音符频率





C4261.63C5523.25
D4293.66D5587.33
E4329.63E5659.25
F4349.23F5698.46
G4392.00G5783.99
A4440.00A5880.00
B4493.88B5987.77

高音区:

音符频率音符频率





C61046.50B61975.53
E61318.51C72093.00
G61567.98E72637.02
A61760.00E85274.04

3.3 半音关系

相邻两个半音,频率比是 2^(1/12) ≈ 1.0595。

# 计算升号音符
def semitone_up(freq):
    return freq * 1.0595def semitone_down(freq):
    return freq / 1.0595# 从 C4 计算 C#4

print(f"C4: {NOTE_C4:.2f}")
print(f"C#4: {semitone_up(NOTE_C4):.2f}")  # 277.18

四、封装一个 BuzzerTest 类

把播放逻辑封装起来,代码更整洁:

class BuzzerTest:
    def __init__(self):
        self.buzzer = None
        self.enabled = False

    def setup(self):
        """初始化蜂鸣器"""
        try:
            self.buzzer = pwmio.PWMOut(board.D5, duty_cycle=0, frequency=440)
            self.enabled = True
            print("✓ 蜂鸣器初始化成功")
            return True
        except Exception as e:
            print(f"✗ 初始化失败: {e}")
            return False

    def play_tone(self, frequency, duration):
        """播放单个音调"""
        if not self.enabled or frequency <= 0:
            time.sleep(duration)
            return

        self.buzzer.frequency = int(frequency)
        duty_cycle = int((VOLUME / 100) * 65535)
        self.buzzer.duty_cycle = duty_cycle
        time.sleep(duration)

    def play_melody(self, melody, gap=0.02):
        """播放旋律(可选间隔)"""
        for freq, dur in melody:
            self.play_tone(freq, dur)
            time.sleep(gap)  # 避免杂音的间隔

为什么要封装

  • 统一管理 PWM 输出

  • 方便调整音量

  • 可以添加公共功能(间隔、音效等)

五、播放音阶

先测试八度音阶,确保能发出正确音高:

def test_scale(self):
    """测试:播放音阶"""
    scale = [
        (NOTE_C4, 0.3), (NOTE_D4, 0.3), (NOTE_E4, 0.3),
        (NOTE_F4, 0.3), (NOTE_G4, 0.3), (NOTE_A4, 0.3),
        (NOTE_B4, 0.4), (NOTE_C5, 0.4)
    ]
    self.play_melody(scale)

听到"do re mi fa so la si do"就对了!

六、经典旋律6.1 小星星

def test_melody(self):
    """测试:小星星"""
    melody = [
        (NOTE_C4, 0.25), (NOTE_C4, 0.25), (NOTE_G4, 0.25), (NOTE_G4, 0.25),
        (NOTE_A4, 0.25), (NOTE_A4, 0.25), (NOTE_G4, 0.5),
        (NOTE_F4, 0.25), (NOTE_F4, 0.25), (NOTE_E4, 0.25), (NOTE_E4, 0.25),
        (NOTE_D4, 0.25), (NOTE_D4, 0.25), (NOTE_C4, 0.5),
    ]
    self.play_melody(melody)

数据结构:[(频率, 时长), ...],时长单位是秒。

6.2 生日歌

def test_birthday(self):
    """生日歌"""
    birthday = [
        (NOTE_C4, 0.25), (NOTE_C4, 0.25), (NOTE_D4, 0.5),
        (NOTE_C4, 0.25), (NOTE_F4, 0.5), (NOTE_E4, 0.75),
        (NOTE_C4, 0.25), (NOTE_C4, 0.25), (NOTE_D4, 0.5),
        (NOTE_C4, 0.25), (NOTE_G4, 0.5), (NOTE_F4, 0.75),
        (NOTE_C4, 0.25), (NOTE_C4, 0.25), (NOTE_C5, 0.5),
        (NOTE_A4, 0.25), (NOTE_F4, 0.25), (NOTE_E4, 0.25),
        (NOTE_D4, 0.25), (NOTE_AS4, 0.25), (NOTE_A4, 0.5),
    ]
    self.play_melody(birthday)

6.3 茉莉花

def test_jasmine(self):
    """茉莉花(简化版)"""
    jasmine = [
        (NOTE_D4, 0.5), (NOTE_E4, 0.25), (NOTE_F4, 0.25),
        (NOTE_G4, 0.5), (NOTE_A4, 0.25), (NOTE_B4, 0.25),
        (NOTE_A4, 0.25), (NOTE_G4, 0.25), (NOTE_F4, 0.25), (NOTE_E4, 0.25),
        (NOTE_D4, 0.5), (NOTE_D4, 0.25), (NOTE_E4, 0.25),
        (NOTE_D4, 0.5), (NOTE_G4, 0.25), (NOTE_G4, 0.75),
    ]
    self.play_melody(jasmine)

6.4 马里奥顶蘑菇音效

这个很有名,就几个音:

def test_mario(self):
    """马里奥音效 - 顶蘑菇"""
    self.play_tone(NOTE_E5, 0.1)
    self.play_tone(NOTE_E5, 0.1)
    self.play_tone(0, 0.1)  # 休止符
    self.play_tone(NOTE_E5, 0.1)
    self.play_tone(0, 0.1)
    self.play_tone(NOTE_C5, 0.1)
    self.play_tone(NOTE_E5, 0.4)

技巧:频率设为 0 就是休止符。

6.5 俄罗斯方块主题曲

def test_tetris(self):
    """俄罗斯方块主题曲"""
    tetris_theme = [
        (NOTE_E5, 0.2), (NOTE_B4, 0.15), (NOTE_C5, 0.15), (NOTE_D5, 0.2),
        (NOTE_C5, 0.15), (NOTE_B4, 0.15), (NOTE_A4, 0.2), (NOTE_A4, 0.15),
        (NOTE_C5, 0.15), (NOTE_E5, 0.2), (NOTE_D5, 0.15), (NOTE_C5, 0.15),
        (NOTE_B4, 0.3), (NOTE_C5, 0.15), (NOTE_D5, 0.2), (NOTE_E5, 0.2),
    ]
    self.play_melody(tetris_theme)

七、8-bit 游戏音效

这部分最好玩!

7.1 吃金币音效

MIDI 风格的经典音效:

def test_midi_coin(self):
    """经典吃金币音效"""
    for _ in range(3):
        self.play_tone(NOTE_E5, 0.08)
        self.play_tone(NOTE_G5, 0.08)
        time.sleep(0.15)

两个音快速交替,非常经典!

7.2 魂斗罗战斗音效

分 8 个阶段,完整还原街机感觉:

def test_contra(self):
    """超级魂斗罗战斗音效 (8阶段)"""
    stages = [
        ("开场急促", [
            (NOTE_E5, 0.08), (NOTE_G5, 0.08), (NOTE_E6, 0.08), (NOTE_G5, 0.08),
        ]),
        ("主旋律A", [
            (NOTE_E6, 0.12), (NOTE_G6, 0.12), (NOTE_E7, 0.12), (NOTE_G6, 0.12),
        ]),
        ("主旋律B", [
            (NOTE_E6, 0.12), (NOTE_G6, 0.12), (NOTE_E7, 0.12), (NOTE_G6, 0.12),
        ]),
        ("紧张段", [
            (NOTE_E6, 0.06), (NOTE_D6, 0.06), (NOTE_C6, 0.06), (NOTE_B5, 0.06),
        ]),
        ("爆发段", [
            (NOTE_E5, 0.10), (NOTE_G5, 0.10), (NOTE_E6, 0.10), (NOTE_G6, 0.10),
        ]),
        ("下行冲刺", [
            (NOTE_G7, 0.08), (NOTE_E7, 0.08), (NOTE_G6, 0.08), (NOTE_E6, 0.08),
        ]),
        ("紧张攀升", [
            (NOTE_E5, 0.08), (NOTE_G5, 0.08), (NOTE_E6, 0.08), (NOTE_G6, 0.08),
        ]),
        ("终止高潮", [
            (NOTE_E5, 0.15), (NOTE_G5, 0.15), (NOTE_E6, 0.15), (NOTE_E7, 0.3),
        ]),
    ]

    for stage_name, melody in stages:
        print(f"  播放: {stage_name}")
        self.play_melody(melody)
        time.sleep(0.2)

7.3 射击音效

def test_shoot(self):
    """射击音效"""
    # 频率快速下降,模拟"啾"的声音
    for freq in range(880, 220, -40):
        self.play_tone(freq, 0.02)

7.4 爆炸/撞击音效

def test_explosion(self):
    """爆炸音效 - 用噪声模拟"""
    # 蜂鸣器不好做真正的噪声,但可以做低频震动
    for _ in range(10):
        self.play_tone(100, 0.05)
        self.play_tone(80, 0.05)

7.5 升级/获取道具音效

def test_powerup(self):
    """升级音效 - 连续上滑"""
    for freq in range(440, 880, 20):
        self.play_tone(freq, 0.03)

八、音效特效8.1 颤音效果 (Vibrato)

快速在两个频率之间切换:

def test_vibrato(self):
    """颤音效果"""
    for _ in range(3):
        self.play_tone(440, 0.05)
        self.play_tone(450, 0.05)

8.2 滑音效果 (Glissando)

频率逐渐变化:

def play_tone_glide(self, start_freq, end_freq, duration):
    """滑音效果"""
    steps = 20
    step_time = duration / steps    for i in range(steps + 1):
        freq = start_freq + (end_freq - start_freq) * i / steps
        self.play_tone(freq, step_time)
        # 使用
        self.play_tone_glide(440, 880, 0.3)  # 上滑八度
        self.play_tone_glide(880, 440, 0.3)  # 下滑八度

8.3 渐强渐弱 (Crescendo/Diminuendo)

def play_tone_fade(self, frequency, duration, fade_in=True):
    """渐强/渐弱播放"""
    steps = 20
    step_time = duration / steps    for i in range(steps):
        factor = (i + 1) / steps if fade_in else (steps - i) / steps
        duty_cycle = int((VOLUME / 100) * 65535 * factor)
        self.buzzer.duty_cycle = duty_cycle
        self.play_tone(frequency, step_time)

8.4 颤音(正弦波调制)

def play_tone_vibrato_modulated(self, center_freq, duration):
    """带颤音的音调(正弦调制)"""
    import math
    steps = 100
    step_time = duration / steps    for i in range(steps):
        t = i * 0.1
        # 颤音:±10Hz 调制
        freq = center_freq + 10 * math.sin(t * 20)
        self.play_tone(freq, step_time)

8.5 PWM 音量控制

def test_pwm_volume(self):
    """PWM 音量对比测试"""
    volume_levels = [5, 10, 20, 50, 80, 100]
    for vol in volume_levels:
        duty_cycle = int((vol / 100) * 65535)
        self.buzzer.duty_cycle = duty_cycle
        time.sleep(0.3)

九、不同音阶风格9.1 大调音阶 (Major)

明亮、快乐的感觉:

# C大调
MAJOR_SCALE = [NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4,
               NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5]

9.2 小调音阶 (Minor)

忧郁、悲伤的感觉:

# C小调(自然小调)
MINOR_SCALE = [NOTE_C4, NOTE_D4, NOTE_D4_SHARP, NOTE_F4,
               NOTE_G4, NOTE_G4_SHARP, NOTE_A4_SHARP, NOTE_C5]
               
# 计算变化音
NOTE_D4_SHARP = NOTE_D4 * 1.0595  # D#
NOTE_G4_SHARP = NOTE_G4 * 1.0595  # G#
NOTE_A4_SHARP = NOTE_A4 * 1.0595  # A#

9.3 五声音阶 (Pentatonic)

很多流行歌和民族音乐用的音阶,只有5个音:

# C五声音阶
PENTATONIC = [NOTE_C4, NOTE_D4, NOTE_E4, NOTE_G4, NOTE_A4, NOTE_C5]

# 特点:任意两个音组合都和谐,适合即兴
def test_pentatonic(self):
    """五声音阶测试"""
    self.play_melody([(freq, 0.3) for freq in PENTATONIC])

9.4 蓝调音阶 (Blues)

在五声音阶基础上加了"蓝调音符":

# C蓝调音阶
BLUES_SCALE = [NOTE_C4, NOTE_D4_SHARP, NOTE_F4, NOTE_G4,
               NOTE_G4_SHARP, NOTE_B4, NOTE_C5]

十、和弦基础

单个音叫音符,多个音同时发出来叫和弦。

10.1 常用和弦

# 三和弦(根音-三度-五度)

CHORD_C = (NOTE_C4, NOTE_E4, NOTE_G4)      # C大三和弦
CHORD_DM = (NOTE_D4, NOTE_F4, NOTE_A4)     # D小三和弦
CHORD_EM = (NOTE_E4, NOTE_G4, NOTE_B4)     # E小三和弦
CHORD_F = (NOTE_F4, NOTE_A4, NOTE_C5)      # F大三和弦
CHORD_G = (NOTE_G4, NOTE_B4, NOTE_D5)      # G大三和弦
CHORD_AM = (NOTE_A4, NOTE_C5, NOTE_E5)     # A小三和弦

10.2 和弦进行

几个经典和弦进行:

# 卡农进行:C - G - Am - GCANON_PROGRESSION = [
    CHORD_C, CHORD_G, CHORD_EM, CHORD_AM,
    CHORD_F, CHORD_C, CHORD_G, CHORD_F,]

def test_chord_progression(self):
    """测试和弦进行(轮流播放)"""
    for chord in CANON_PROGRESSION:
        for freq in chord:
            self.play_tone(freq, 0.3)

10.3 琶音 (Arpeggio)

把和弦的音依次弹出来:

def test_arppegio(self, chord, tempo=0.15):
    """琶音播放"""
    for freq in chord:
        self.play_tone(freq, tempo)

十一、节奏模式11.1 拍子基础

# 假设以四分音符为一拍,每分钟120拍
# 每拍时长 = 60 / 120 = 0.5 秒

TEMPO_120 = 0.25  # 四分音符时长(120 BPM)
TEMPO_90 = 0.33   # 四分音符时长(90 BPM)
TEMPO_60 = 0.5    # 四分音符时长(60 BPM)

# 八分音符 = 四分音符的一半
EIGHTH_NOTE = TEMPO_120 / 2

11.2 常见节奏型

def test_rhythm_patterns(self):
    """各种节奏型测试"""

    # 1. 平均节奏(行进感)
    marching = [(freq, 0.25) for freq in [NOTE_C4, NOTE_E4, NOTE_G4] * 2]

    # 2. 切分音(节奏感强)
    syncopation = [
        (NOTE_C4, 0.2), (NOTE_E4, 0.1), (NOTE_G4, 0.2),
        (NOTE_G4, 0.1), (NOTE_E4, 0.2), (NOTE_C4, 0.2),
    ]

    # 3. 三连音(圆舞曲感)
    triplets = []
    for _ in range(4):
        triplets.extend([(NOTE_C4, 0.08), (NOTE_E4, 0.08), (NOTE_G4, 0.08)])

    # 4. 附点节奏(悠扬感)
    dotted = [
        (NOTE_C4, 0.45), (NOTE_E4, 0.15), (NOTE_G4, 0.3),
        (NOTE_E4, 0.45), (NOTE_G4, 0.15), (NOTE_C4, 0.3),
    ]

十二、实战:根据颜色播放音乐

这是"拾颜播放器"项目的核心功能:

# 颜色到情感的映射COLOR_MUSIC = {
    "red": {
        "name": "炽热红",
        "tempo": 0.12,      # 快节奏
        "scale": "major",   # 大调
        "melody": [         # 热情洋溢的旋律
            (NOTE_E5, 0.12), (NOTE_G5, 0.12), (NOTE_E6, 0.12), (NOTE_G5, 0.12),
            (NOTE_E5, 0.12), (NOTE_G5, 0.12), (NOTE_E6, 0.24),
        ]
    },
    "blue": {
        "name": "深海蓝",
        "tempo": 0.35,      # 慢节奏
        "scale": "minor",   # 小调
        "melody": [         # 舒缓的旋律
            (NOTE_A3, 0.35), (NOTE_C4, 0.35), (NOTE_E4, 0.35),
            (NOTE_A3, 0.35), (NOTE_C4, 0.35), (NOTE_E4, 0.7),
        ]
    },
    "green": {
        "name": "清新绿",
        "tempo": 0.25,
        "scale": "pentatonic",
        "melody": [         # 五声音阶,清新感
            (NOTE_E4, 0.25), (NOTE_G4, 0.25), (NOTE_A4, 0.25),
            (NOTE_C5, 0.25), (NOTE_D5, 0.25), (NOTE_E5, 0.5),
        ]
    },
    "yellow": {
        "name": "温暖黄",
        "tempo": 0.3,
        "scale": "major",
        "melody": [         # 明亮温暖
            (NOTE_C4, 0.3), (NOTE_E4, 0.3), (NOTE_G4, 0.3),
            (NOTE_C5, 0.6),
        ]
    },
    "purple": {
        "name": "神秘紫",
        "tempo": 0.4,
        "scale": "blues",
        "melody": [         # 蓝调风格
            (NOTE_A3, 0.2), (NOTE_C4, 0.2), (NOTE_D4_SHARP, 0.2),
            (NOTE_F4, 0.2), (NOTE_G4, 0.2), (NOTE_A4, 0.8),
        ]
    },}def play_color_music(self, color_name):
    """根据颜色播放音乐"""
    if color_name not in COLOR_MUSIC:
        return

    config = COLOR_MUSIC[color_name]
    print(f"播放: {config['name']}")

    for freq, base_dur in config['melody']:
        actual_dur = base_dur * config['tempo']
        self.play_tone(freq, actual_dur)
        time.sleep(0.02)  # 间隔

十三、常见问题Q1: 声音太小

检查 VOLUME 设置,建议 50-80。如果还小,检查硬件连接。

Q2: 播放时有杂音

在播放间隔加短暂静音:

def play_melody(self, melody):
    for freq, dur in melody:
        self.play_tone(freq, dur)
        time.sleep(0.02)  # 短暂间隔,避免杂音

Q3: 高音不稳定

高频音使用较短的时长。

Q4: 节奏不准

用固定的 tempo 变量控制,不要手写时长:

TEMPO = 0.25  # 四分音符melody = [
    (NOTE_C4, TEMPO),     # 一拍
    (NOTE_E4, TEMPO/2),   # 半拍
    (NOTE_G4, TEMPO/2),   # 半拍
    (NOTE_C5, TEMPO*2),   # 两拍]

Q5: 想演奏更长的曲子

把旋律数据放在单独的文件里:

# melodies.pyTETRIS_THEME = [
    (NOTE_E5, 0.2), (NOTE_B4, 0.15), (NOTE_C5, 0.15), (NOTE_D5, 0.2),
    # ... 更多音符]

# main.py
from melodies import TETRIS_THEME
self.play_melody(TETRIS_THEME)

十四、总结

通过 CircuitPython 的 PWM 模块,蜂鸣器可以实现:

  1. 基础功能:播放准确音高的音符

  2. 旋律演奏:演奏完整歌曲

  3. 游戏音效:还原经典 8-bit 音效

  4. 音效特效:颤音、滑音、渐强渐弱

  5. 情感表达:不同颜色对应不同音乐风格

关键要点:

  • 记住标准音 A4=440Hz,可以用公式推导其他音

  • 理解大调、小调、五声音阶的情绪区别

  • 节奏比音高更重要

  • 多听多练,培养乐感


十五、播放效果

微信图片_20251227112813_189_4582.png


视频展示:https://www.bilibili.com/video/BV1y1viBcE8v


在"拾颜播放器"项目中,蜂鸣器负责把颜色检测结果转换成音乐输出,配合屏幕显示和颜色传感器,完成了"颜色→情感→音乐"的完整交互体验。





关键词: 三期     拾色     播放器     蜂鸣器     频率    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]