144 lines
4.6 KiB
Python
144 lines
4.6 KiB
Python
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
|