纯代码合成音效、混音并做响度验收,不需要任何素材文件。主要用于给代码生成的视频配声,输出可直接交给 sys.mediaFoundation.writer 写入 MP4。
av.audio:音频片段 av.audio.clip(合成、滤波、效果、剪辑、输出、分析)与工具函数。av.audio.sfx:24 种常用音效预设(whoosh、pop、impact、riser……)。av.audio.mixer:时间线混音器,按时间放置音效和音乐,自动闪避、响度归一化和真峰值限幅。av.audio.music:程序化配乐,按风格、速度、段落强度和重音现场合成背景音乐。av.audio.dsp:DSP 内核(C 函数,预编译成 DLL 内嵌在库里、内存加载,运行时不需要 tcc;C 源码与重建脚本在库目录的 .res/),一般不直接使用。音频内部是 float32 平面 PCM(1 或 2 声道),默认 48000Hz。所有随机性来自 seed,同样的代码总是得到逐采样相同的声音。生成视频是耗时任务,请放在后台线程运行。
同一张 cue 表同时驱动画面和声音。音效的“命中点”(anchor)对齐到 cue 时间:pop、click、impact 在起音处,whoosh 在最响处,riser、swell 在结束处。
import av.audio.mixer;
import sys.mediaFoundation.writer;
import gdip.graphics;
var W,H,FPS,SR,DUR = 1280,720,30,48000,6;
var cues = {{1.0,"whoosh"};{1.6,"pop"};{3.0,"impact"};{4.5,"ding"}};
var mix = av.audio.mixer(DUR);
for(i,c in cues) mix.sfx(c[1],c[2]);
var sound = mix.render(); //默认归一化到 -16 LUFS、真峰值不超过 -1.5 dBTP
var writer = sys.mediaFoundation.writer("/demo.mp4",{width=W;height=H;fps=FPS;audio={sampleRate=SR;channels=2}});
var bmp = gdip.bitmap(W,H,0xE200B/*PARGB*/);
var g = gdip.graphics(bmp);
for(f=0;DUR * FPS - 1){
g.clear(0xFF202020);
//……按 f / FPS 绘制画面
writer.writeFrame(bmp);
//这一帧对应的采样范围用整数算法,任何帧率都不会累积误差
var s0 = math.floor(f * SR / FPS);
var s1 = math.floor((f + 1) * SR / FPS);
writer.writeAudio(sound.pcm16(s0,s1 - s0,{channels=2}));
}
writer.close();
验收时可以直接看 sound.report(响度、真峰值、限幅衰减、各总线响度与闪避量),或用 sound.onsets() 核对声音起音是否落在 cue 时间。
av.audio.sfx(name,opt) 返回一个 av.audio.clip,已按典型用法归一化到固定响度(最大瞬时响度,见 av.audio.sfx.levels),峰值不超过 -1dBFS。在混音器里用 mix.sfx(t,name,opt),同名音效每次自动换一个变体。
| 名称 | 命中点 | 用途 | 专用参数 |
|---|---|---|---|
| whoosh | 最响处 | 转场、飞入 | dur(0.8) peak(0.6) dir(1 左→右,-1,0) |
| swish | 最响处 | 滑动、切换 | dur(0.28) dir |
| pop / bubble | 起音 | 元素弹出 / 气泡 | pitch |
| click / tick / blip | 起音 | 界面点击 / 滴答 / 哔 | pitch,blip 可用 note |
| ding / chime / sparkle | 起音 | 铃声 / 琶音 / 闪烁 | note decay;chime: notes gap;sparkle: dur count |
| success / notify / error / coin | 起音 | 状态提示 | note 或 pitch |
| impact / boom / drop | 起音 | 标题落地 / 轰鸣 / 低音下潜 | dur pitch |
| riser / swell | 结束处 | 蓄势 / 反向镲 | dur |
| downer | 起音 | 释放 | dur |
| glitch | 起音 | 数字故障 | dur |
| type / typing | 起音 | 单次击键 / 连续打字 | typing: count interval dur |
| shutter | 起音 | 快门 | pitch |
通用参数:seed(变体)、gain(dB,在预设响度基础上调整)、pan(-1~1)、pitch(半音)、reverb(湿声量,0 关闭)、normalize=false(不归一化)。
自定义预设:给 av.audio.sfx.presets 添加 function(opt){ return clip },并在 av.audio.sfx.levels 里设置目标响度。
import av.audio.mixer;
var mix = av.audio.mixer(10); //总时长 10 秒
mix.sfx(2.0,"riser",{dur=1.5}); //在 2.0 秒结束的蓄势
mix.sfx(2.0,"impact"); //蓄势结束处落地
var voice = av.audio.load("/voice.mp3"); //读取音频文件(旁白等),失败返回 null
if(voice) mix.add(5.0,voice,{bus="voice";gain=-2});
var bed = av.audio.clip(10,2).osc("saw","A2",{amp=0.1;unison=5}).lp(700);
mix.music(bed,{gain=-6;fadeOut=2}); //music 总线:其他总线有声音时自动压低 4dB
var out = mix.render({lufs=-14}); //渲染:响度 -14 LUFS
out.saveWav("/mix.wav");
mix.bus(name,{gain;duck}) 设置总线。duck 为 {amount=最大衰减 dB;by={触发总线};threshold=-45;range=15;attack=0.01;release=0.3},false 关闭。render 顺序:总线增益 → 闪避 → 汇总 → 低切 25Hz → 响度归一化 → 真峰值限幅(限幅使响度不足时补偿,最多衰减 maxLimit=6dB)→ 结尾淡出 0.05 秒。render 可重复调用(不修改已放置的内容),mix.events 记录所有放置事件。sound.report 还会给出 eventsBySource、每个总线的 lufs/afterDuckLufs/duckReduction/duckDrop。需要做声音预检时可写 mix.render({bar=2;barOffset=0.1;eventMetrics=true}),得到每小节响度与每个非配乐事件的局部窗口指标;eventMetrics 在 duck 后、母带低切/响度归一/限幅之前计算,delta 是该窗口混合总线相对音乐总线的增量,不含母带统一增益;这些是编码前 PCM 指标,不是 AAC 回读结果。按风格、速度(bpm)、段落强度(energy 0~3)和重音现场合成背景音乐,不需要任何素材。返回立体声 av.audio.clip,响度 -16 LUFS,可直接交给 mix.music。在 av.film 里写 music="tech" 即可,配乐会按时间线自动编排。
import av.audio.music;
var m = av.audio.music("tech",{
duration = 20; //秒(必填)
bpm = 120; //省略则用风格默认值
sections = {{t=0;energy=1};{t=4;energy=3};{t=12;energy=2}}; //段落强度 0~3
hits = {{t=4}}; //重音:加镲和底鼓
endAt = 18; //终止和弦(主和弦长音一直响到结尾)
});
m.saveWav("/music.wav");
return {bpm = m.bpm;key = m.key;endAt = m.endAt;bars = table.slice(m.bars,1,3)}; //bars、beats 供画面卡点
| 风格 | 默认速度与调 | 特点 |
|---|---|---|
| tech | 124 BPM,A 小调 | 四拍底鼓、离拍贝斯、琶音,垫音随底鼓抽吸(i–VI–III–VII) |
| upbeat | 112 BPM,C 大调 | 电钢琴和弦、拨弦琶音、沙锤、拍手(I–V–vi–IV) |
| ambient | 76 BPM,D 大调 | 慢起圆润垫音、铃、木琴琶音、低音,几乎没有鼓(大七和弦,垫音开放排法) |
| lofi | 84 BPM,F 大调 | 摇摆节奏、暖音电钢琴七和弦、黑胶底噪(ii–V–I–vi) |
sections 时:前奏 2 小节后进入高潮;较长的曲子中段回落 2 小节。省略 endAt 时,终止和弦落在距结尾至少 1.5 秒的最后一个小节线上。key(如 "A minor"、"Bb"、"F# dorian")、seed(变体)、lufs、stems=true(返回值的 stems 为各总线片段、events 为音符事件表,便于检查)。beats、bars(节拍、小节时刻)、sections、bpm、key、endAt、notes(音符数)。mode="continuous" 固定连续配乐床,energy 指定固定层级;autoAccents=false 关闭段落自动重音,hits 保留手动命中点。hitMergeWindow 合并同一命中点附近的重复重音,手动命中优先。beatOffset 同时影响 beats/bars 与 film 的拍点检查。mix 表自动平衡到相对响度(测门限整合响度,与乐器实现无关),再做侧链抽吸、总线压缩、响度归一与真峰值限幅。20 秒配乐约 3 秒。自定义风格:复制 av.audio.music.styles.tech 改成新表,放进 av.audio.music.styles 即可。节奏型是 16 步字符串(每步 1/16 拍;X 重音、x 正常、o 轻、. 休止;贝斯的 "S" 表示整小节长音),按强度 0~3 分别写;pad 的值是亮度 0~1,bell 的值是每个八分音符出现的概率。乐器在 av.audio.music.instruments 里,每个函数返回一个单声道片段;sounds 表给乐器传音色参数,例如垫音 {voices=3;spread=8;poles=4;cutoff={400;1400}}(齐奏数、失谐音分、24dB 低通、截止频率),琶音 {tone="mallet"}(木琴类,最圆润)或 {tone="ks"}(拨弦)。
配乐的音色规则(来自试听反馈与客观测量,自定义风格时也适用):
padVoicing="close" 关闭)。av.audio.clip(秒数,声道数=1) 创建静音片段,多数方法返回片段自身,可以链式调用。滤波、包络等处理作用于整个片段,要分别处理的声音层先各自生成再 mix 到一起:
import av.audio;
//一个电影感冲击:下潜正弦 + 噪声瞬态 + 混响
var hit = av.audio.clip(1.2);
hit.osc("sine",{{0,120};{0.07,50,"exp"};{0.8,38,"exp"}},{amp=av.audio.perc(0.002,0.9)});
var crack = av.audio.clip(0.3).noise("white").lp({{0,8000};{0.2,400,"exp"}}).amp(av.audio.perc(0.001,0.25,-6));
hit.mix(crack,0,{gain=-6}).drive(1.8).reverb({mix=0.2;room=0.8});
hit.normalize(-3).saveWav("/hit.wav");
可自动化的参数(频率、幅度、截止频率、声像、FM 指数、dB 增益)可写:
"A4"、"C#5"、"Bb3";{{时间,值},{时间,值,形状},...},形状写在段末点上:省略为线性,"exp" 指数(适合频率),"hold" 阶跃,"sine" S 形,数值为曲率(负数先快后慢,适合衰减;正数先慢后快,适合渐强);function(t){ return 值 },每 32 个采样求值一次后线性插值,例如 av.audio.lfo(6,0.3)。曲线时间从处理区域起点算起。常用包络:av.audio.perc(起音,衰减,曲率)、av.audio.adsr(a,d,s,r,总时长)。
| 类别 | 方法 |
|---|---|
| 发声(叠加) | osc(wave,freq,opt) wave: sine/tri/saw/square,opt: amp pw detune unison spread seed phase;noise("white"/"pink"/"brown",opt);fm(freq,{ratio;index;feedback});pluck(freq,{decay;bright}) 拨弦;modal(freq,{{倍频,幅度,衰减秒},...}) 钟/木琴;dust(每秒个数) |
| 增益包络 | amp(值或曲线)、gain(dB)、fade(in,out)、normalize(目标,"peak"/"lufs"/"momentary") |
| 滤波 | lp hp bp notch(freq,q)、eq(freq,dB,q)、lowShelf/highShelf(freq,dB),12dB/倍频,频率可自动化 |
| 失真 | drive(量,"tanh"/"clip"/"fold"/"tube",mix)、crush(位数,保持采样数) |
| 声像 | pan(-1~1 或曲线)(单声道转立体声,恒定功率,居中时两声道等于原声)、stereo()、mono()、width(w) |
| 空间(会延长片段) | reverb({mix;room;damp;predelay;lowcut;highcut;tail})、delay(秒,{feedback;mix;damp;pingpong})、chorus({rate;depth;mix}) |
| 动态 | compress({threshold;ratio;knee;attack;release;makeup;sidechain})、limit({ceiling;truePeak;lookahead;release}) |
| 剪辑 | mix(src,时间,{gain;pan})(src.anchor 对齐到时间)、copy()、slice(t0,t1)、resize(帧)、extend(秒)、pad(前,后)、trim()、reverse()、clear() |
| 输出 | pcm16(起始帧,帧数,{channels;dither})、saveWav(path,{bits=16/32});av.audio.load(音频文件[,秒数])、av.audio.fromPcm16(pcm,声道数,采样率) |
av.audio.load 读 WAV、MP3、M4A/AAC、WMA 和 MP4/MOV 的音轨,返回的片段总是 av.audio.sampleRate(默认 48000)、1 或 2 声道:同采样率的 WAV 直接解析(逐采样不变),其他交给系统 Media Foundation 解码并重采样,可以直接进混音器。可选第 2 个参数只读开头若干秒(长音乐只取用得到的部分)。
| 分析 | analyze()、loudness()、peak()、truePeak()、rms()、bands()、onsets() |
大多数处理方法的最后一个参数 opt 可用 start、dur(秒)限定处理区域。
loudness() 按 ITU-R BS.1770-4 返回 整合响度 LUFS(绝对/相对门限)、最大瞬时响度(400ms)、最大短期响度(3s)。单声道片段按居中放置(两声道相同)计算;不足 400ms 的片段整段作为一个块。truePeak() 为 4 倍过采样真峰值(dBTP)。突然开始或截断的声音在边界处会有真实的过冲。onsets() 返回起音时间(秒),精确到采样附近,用于核对音画同步;对安静背景上的瞬态声音最可靠。analyze() 汇总 peak、truePeak、rms、lufs、momentaryMax、shortTermMax、dc、clipped(满幅采样数)、start/end(首尾超过 -60dBFS 的时间)。bands() 返回低(<200Hz)、中(200~2k)、高(2k~8k)、极高(>8k)频段能量占比,可粗查音色是否合理。实测(本机):混音逐帧写入 MP4 后回读,AAC 起音误差 ≤ 0.5ms,响度变化 < 0.05 LU,画面闪白帧与声音起音在同一帧;30 秒、含 24 个音效和音乐床的混音渲染约 3 秒。
av.audio.freq("A4") 音名转频率,av.audio.midi("C4")、av.audio.mtof(60),av.audio.dbToAmp(db)、av.audio.ampToDb(amp),av.audio.perc、av.audio.adsr、av.audio.lfo,av.audio.random01(a,b,c) 确定性随机数。