You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
70 lines
2.4 KiB
70 lines
2.4 KiB
import 'dart:math' as math;
|
|
import 'dart:typed_data';
|
|
|
|
/// AI 对话的提示音:唤醒「叮」、结束「咚」。
|
|
///
|
|
/// 现合成而不是打包音频资源,理由有三:
|
|
/// - 这两个音要**在耳机里**响,得走 AI 下行通道以 16k/16bit/mono 的裸 PCM 推下去,
|
|
/// 打包成 mp3/wav 还得先解码,白绕一圈;
|
|
/// - 两个正弦加包络一共几十行,比维护资源文件省事;
|
|
/// - 采样率跟着 [sampleRate] 走,将来下行换采样率不用重新导资源。
|
|
///
|
|
/// 包络两端都做了淡入淡出——直接给方波般的起止会有"咔"的爆音,
|
|
/// 在这条链路上很容易被误当成杂音。
|
|
class CueTone {
|
|
CueTone._();
|
|
|
|
/// 唤醒提示音:短促、偏高,一听就是"开始了"
|
|
static Uint8List ding({int sampleRate = 16000}) => _tone(
|
|
sampleRate: sampleRate,
|
|
freqHz: 1180,
|
|
durationMs: 150,
|
|
// 叠一个高八度的泛音,听感更"清脆",不然纯正弦发闷
|
|
overtoneHz: 2360,
|
|
overtoneGain: 0.28,
|
|
);
|
|
|
|
/// 结束提示音:低一些、稍长,收尾感
|
|
static Uint8List dong({int sampleRate = 16000}) => _tone(
|
|
sampleRate: sampleRate,
|
|
freqHz: 520,
|
|
durationMs: 230,
|
|
overtoneHz: 1040,
|
|
overtoneGain: 0.18,
|
|
);
|
|
|
|
/// [amplitude] 取 0.35:提示音不该盖过对话本身,也别小到听不见
|
|
static Uint8List _tone({
|
|
required int sampleRate,
|
|
required double freqHz,
|
|
required int durationMs,
|
|
double overtoneHz = 0,
|
|
double overtoneGain = 0,
|
|
double amplitude = 0.35,
|
|
}) {
|
|
final total = sampleRate * durationMs ~/ 1000;
|
|
final bytes = Uint8List(total * 2);
|
|
final view = ByteData.view(bytes.buffer);
|
|
|
|
// 5ms 淡入避免起始爆音;衰减用指数,听起来像敲击而不是被硬切
|
|
final fadeIn = math.max(1, sampleRate * 5 ~/ 1000);
|
|
|
|
for (var i = 0; i < total; i++) {
|
|
final t = i / sampleRate;
|
|
final progress = i / total;
|
|
|
|
var sample = math.sin(2 * math.pi * freqHz * t);
|
|
if (overtoneHz > 0) {
|
|
sample += overtoneGain * math.sin(2 * math.pi * overtoneHz * t);
|
|
sample /= (1 + overtoneGain);
|
|
}
|
|
|
|
var env = math.exp(-4.5 * progress);
|
|
if (i < fadeIn) env *= i / fadeIn;
|
|
|
|
final v = (sample * env * amplitude * 32767).round().clamp(-32768, 32767);
|
|
view.setInt16(i * 2, v, Endian.little);
|
|
}
|
|
return bytes;
|
|
}
|
|
}
|
|
|