This commit is contained in:
jeremygan2021
2026-03-02 21:14:05 +08:00
commit 252a430466
26 changed files with 4591 additions and 0 deletions

143
audio.py Normal file
View File

@@ -0,0 +1,143 @@
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