音频服务
AudioService 私有持有音频源、用于分析的 SoundData、谱面时间、播放状态、速度、音量与当前解码任务。它不读取 music、music_data、music_play、time 或 beat 全局。启动入口注入 LÖVE 后端与谱面接口,测试可以提供内存实现。
时间与播放
所有时间接口都用秒。谱面时间为 getCurrentTime();音频位置为谱面时间减去谱面的 offset/1000。getDuration() 是音频长度加 offset 后的谱面终点;getRawDuration() 是音频原长度。编辑器定位限制在谱面起点和终点之间;菜单预览从原音频第 0 秒试听,因此负 offset 可对应负的谱面时间。正 offset 时,播放时钟先推进,达到音频起点后音频才开始发声。
seek(seconds, {pause=true}) 暂停并定位,setCurrentBeat(value, options) 按当前 BPM 表定位。当前节拍、总节拍始终从时钟和谱面计算;修改 BPM/offset、撤销重做和切换谱面后不会保留另一份旧的节拍状态。缓存仅用于复用同一位置的换算,接收谱面变更通知后失效。
pause() 立即暂停原生音频源;resume() 恢复;stop() 停止并将谱面时间归零。播放到结尾自动暂停并停在结尾,再次恢复时从起点开始。setRate(rate) 同时影响音频音高/速度和谱面时间推进,setVolume(value) 使用 0 到 1 的音量,读取当前值用 getRate()/getVolume()。main.lua 统一调用 update(dt),其他模块不要重复推进时钟。暂停期间不反复定位音频;播放偏差达到 50ms 时才补正。
加载与资源归属
load(path, {preview=true}) 创建流式音频源并替换当前歌曲,菜单预览循环播放。默认请求后台解码;每个任务拥有独立通道,启动入口提供文件快照供解码使用,不依赖菜单临时挂载的目录;换歌或卸载后不会接收旧任务的结果。load 返回 true 或 false, error,音频源构造失败时保留原资源。分析数据解码失败可通过 getLoadError() 或 audio:error 取得错误,不能把尚未解码完成误判为没有音频源。
进入编辑器调用 prepareEditor():已有 SoundData 时直接复用,尚未完成时同步解码并作废旧任务引用,随后关闭预览循环、暂停并定位到谱面起点。菜单 FFT、波形、声纹图和 BPM 测量共享服务提供的数据,不分别加载同一歌曲。
unload() 停止并释放当前 Source,清空数据和任务引用。SoundData 的已有分析使用方可以继续持有自己的引用,服务不会在工作线程使用期间强行释放它。均衡器通过 setEffect(name, parameters) 设置效果,传 false 关闭;已开启的效果会应用到下一首歌曲。
setSource(source) 用于接管外部构造的源,例如内存音频;它从源读取时长并重置播放状态。setSoundData(data) 提供相应的分析数据。getSource() 保留给兼容或底层扩展,返回的原生对象不应由调用方直接执行播放/暂停/定位,避免绕过服务时钟。常规插件只需服务命令和只读的 getSoundData()。
插件迁移与通知
旧的音频全局及 beat.nowbeat/allbeat 已移除。beat 仍提供节拍格式、分度和 BPM 换算工具,播放位置则通过 ctx.audio 访问。setCurrentTime 委托定位,setPlaying 委托原生播放/暂停;不再保留独立的 setDuration/setAllBeat,时长由资源和 offset 推导,总节拍由 BPM 表推导。
local unsubscribe
return {
name = 'audio example',
init = function(ctx)
unsubscribe = ctx.eventBus:on('audio:decoded', function(event)
local data = ctx.audio:getSoundData() -- 数据可能为空,先判断再分析。
if data then
-- 根据 event.revision 更新本插件的分析缓存。
end
end)
end,
destroy = function()
if unsubscribe then unsubscribe(); unsubscribe = nil end
end,
}| 通知 | 参数内容 |
|---|---|
audio:loaded | {path, revision},资源替换或卸载后广播;卸载时 path 为空 |
audio:decoded | {path, revision},分析数据更新后广播 |
audio:playing_changed | {playing},服务播放状态变化后广播 |
audio:error | {path, error},加载、解码或效果设置失败后广播 |
服务通过 init({chart, backend, events, report}) 装配;chart 提供 getOffset/getBpmCount/toBeat/toTime,backend 提供 newSource/newSoundData/newThread/newChannel/setEffect,可选的 readAudio(path) 返回供解码使用的文件快照。AudioService.new(options) 创建独立实例,各实例不共享音频状态。纯内存测试可不传事件总栈,此时节拍每次现算。
验证
tests/audio_service.lua 验证独立状态、即时暂停、定位、offset、变速、资源替换、失败处理、旧解码结果隔离和效果。love tests/audio_service_love 使用真实 Source 与线程验证流式加载、解码复用和播放生命周期,仅在测试目录临时写入合成 WAV,结束后删除。
love tests/editor_input_love 验证服务迁移后的进度条、滚轮、菜单及波形/声纹图绘制;love tests/bpm_love 验证测量插件读取服务数据及结果应用/撤销;love tests/spectrogram_love 验证渲染、缩放和音频替换。