Files
V2_micropython/audio.py
jeremygan2021 252a430466 f
2026-03-02 21:14:05 +08:00

144 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from machine import I2S, Pin
import struct
import time
import math
from config import CURRENT_CONFIG
class AudioPlayer:
def __init__(self):
self.i2s = None
self.config = None
if hasattr(CURRENT_CONFIG, 'audio') and CURRENT_CONFIG.audio.get('enabled', False):
self.config = CURRENT_CONFIG.audio
self._init_audio()
else:
print("Audio not enabled in config")
def _init_audio(self):
"""初始化音频输出"""
# 从配置中获取引脚
bck = self.config.get('bck')
ws = self.config.get('ws')
sd = self.config.get('sd')
sample_rate = self.config.get('sample_rate', 24000)
print(f"Init Speaker: BCK={bck}, WS={ws}, SD={sd}")
try:
# MAX98357A 配置尝试:
# 使用 I2S.STEREO 格式通常更稳定MAX98357A 会自动混合 L+R
self.i2s = I2S(
0,
sck=Pin(bck),
ws=Pin(ws),
sd=Pin(sd),
mode=I2S.TX,
bits=16,
format=I2S.STEREO, # 修改为 STEREO
rate=sample_rate,
ibuf=20000,
)
except Exception as e:
print(f"Speaker init failed: {e}")
self.i2s = None
def play_tone(self, frequency, duration_ms, volume=0.5):
"""播放指定频率的音调"""
if self.i2s is None: return
sample_rate = self.config.get('sample_rate', 24000)
n_samples = int(sample_rate * duration_ms / 1000)
amplitude = int(32767 * volume)
# STEREO: 每个采样 2 个声道 (L+R),每个声道 2 字节 (16-bit) -> 4 字节/帧
buffer = bytearray(n_samples * 4)
if frequency > 0:
period = sample_rate // frequency
half_period = period // 2
for i in range(n_samples):
# 方波:前半周期高电平,后半周期低电平
sample = amplitude if (i % period) < half_period else -amplitude
# 左声道
struct.pack_into('<h', buffer, i * 4, sample)
# 右声道
struct.pack_into('<h', buffer, i * 4 + 2, sample)
else:
# 静音缓冲区默认为0
pass
try:
# 写入多次以确保缓冲区填满并开始播放
self.i2s.write(buffer)
except Exception as e:
print(f"Write error: {e}")
def play_mario(self):
"""播放马里奥主题曲片段"""
if self.i2s is None: return
print(">>> Playing Mario Theme...")
# Note frequencies
NOTE_E5 = 659
NOTE_C5 = 523
NOTE_G5 = 784
NOTE_G4 = 392
# (frequency, duration_ms)
# 马里奥主题曲开头
melody = [
(NOTE_E5, 150), (NOTE_E5, 150), (0, 150), (NOTE_E5, 150),
(0, 150), (NOTE_C5, 150), (NOTE_E5, 150), (0, 150),
(NOTE_G5, 150), (0, 450),
(NOTE_G4, 150), (0, 450)
]
for freq, duration in melody:
if freq == 0:
time.sleep_ms(duration)
else:
self.play_tone(freq, duration, 0.3)
# 短暂的停顿,避免音符粘连
time.sleep_ms(10)
class Microphone:
def __init__(self):
self.i2s = None
self.config = None
if hasattr(CURRENT_CONFIG, 'mic') and CURRENT_CONFIG.mic.get('enabled', False):
self.config = CURRENT_CONFIG.mic
self._init_mic()
else:
print("Mic not enabled in config")
def _init_mic(self):
"""初始化麦克风"""
# 从配置中获取引脚
sck = self.config.get('sck')
ws = self.config.get('ws')
sd = self.config.get('sd')
sample_rate = self.config.get('sample_rate', 16000)
print(f"Init Mic: SCK={sck}, WS={ws}, SD={sd}")
try:
self.i2s = I2S(
1,
sck=Pin(sck),
ws=Pin(ws),
sd=Pin(sd),
mode=I2S.RX,
bits=32, # ICS-43434 需要 32位 时钟周期
format=I2S.MONO,
rate=sample_rate,
ibuf=20000,
)
except Exception as e:
print(f"Mic init failed: {e}")
self.i2s = None
def readinto(self, buf):
"""读取数据到缓冲区"""
if self.i2s:
return self.i2s.readinto(buf)
return 0