aardio 文档

av.audio

纯代码合成音效、混音并做响度验收,不需要任何素材文件。主要用于给代码生成的视频配声,输出可直接交给 sys.mediaFoundation.writer 写入 MP4。

音频内部是 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

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 里设置目标响度。

混音器 av.audio.mixer

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");

配乐 av.audio.music

按风格、速度(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)

自定义风格:复制 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"}(拨弦)。

配乐的音色规则(来自试听反馈与客观测量,自定义风格时也适用):

音频片段 av.audio.clip

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 增益)可写:

曲线时间从处理区域起点算起。常用包络: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(秒)限定处理区域。

分析与验收

实测(本机):混音逐帧写入 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) 确定性随机数。

Markdown 格式