f
This commit is contained in:
143
audio.py
Normal file
143
audio.py
Normal 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
|
||||
Reference in New Issue
Block a user