flutter_sequencer
这个Flutter插件允许您设置采样器乐器,并创建在这些乐器上播放音符的多轨序列。您可以为序列指定循环范围,并安排音量自动化。
它在Android和iOS上都使用了AudioKit的核心采样器引擎,这允许您通过加载一些采样并指定它们的音符来创建一个乐器。如果您播放了一个没有采样对应的音符,它将通过音高调整您的其他采样来填补空白。它还支持在两个平台上播放SF2(SoundFont)文件,并且在iOS上,您可以加载任何AudioUnit乐器。
示例应用程序是一个鼓机。理论上,您也可以使用此插件制作一个完整的基于采样的DAW。您也可以将其用于游戏音效,甚至生成动态游戏配乐。

如何使用
创建序列
final sequence = Sequence(tempo: 120.0, endBeat: 8.0);
创建序列时,您需要设置节奏和结束节拍。
创建乐器
final instruments = [
Sf2Instrument(path: "assets/sf2/TR-808.sf2", isAsset: true),
SfzInstrument(path: "assets/sfz/SplendidGrandPiano.sfz", isAsset: true),
SamplerInstrument(
id: "80's FM Bass",
sampleDescriptors: [
SampleDescriptor(filename: "assets/wav/D3.wav", isAsset: true, noteNumber: 62),
SampleDescriptor(filename: "assets/wav/F3.wav", isAsset: true, noteNumber: 65),
SampleDescriptor(filename: "assets/wav/G#3.wav", isAsset: true, noteNumber: 68),
]
),
];
一个乐器可以用于创建一条或多条音轨。
有四种乐器
- SamplerInstrument,用于在AudioKitSampler中手动加载SampleDescriptors列表,而无需SFZ文件
- 在iOS和Android上,它将由 AudioKit Sampler 播放
- SampleDescriptor可以指向.wav或.wv(WavPack)文件。我推荐尽可能使用.wv,
因为它支持无损压缩。使用ffmpeg可以轻松地将音频文件转换为WavPack
格式。 - 您只需要指定样本的路径、它是否是资源,
以及它对应的音符编号。 - 可选地,您可以为每个样本设置一个音符范围或一个力度范围。您可以这样创建
"力度层",其中较低的力度触发的样本与较高的力度不同。
- SfzInstrument,用于加载
.sfzSFZ文件。- 这也将由AudioKit采样器播放。
- 仅支持少数SFZ opcodes。请查看sfz_parser.dart以了解哪些opcodes
被识别。 - 这不是一个功能齐全的SFZ播放器。SFZ只是提供了一种方便的格式将采样
加载到采样器中。
- Sf2Instrument,用于加载
.sf2SoundFont文件。- 在iOS上,它将由内置的Apple MIDI合成器AudioUnit播放
- 在Android上,它将由 tinysoundfont 播放
- AudioUnitInstrument,用于加载AudioUnit
- 这仅在iOS上有效
对于SF2或SFZ乐器,将isAsset: true传递给Flutter资源目录中的路径。
您应该为“工厂预设”声音使用资源。要从文件系统加载用户提供或下载的声音
,请传递isAsset: false。
可选:保持引擎运行
GlobalState().setKeepEngineRunning(true);
即使所有序列都暂停,这也会使音频引擎保持运行。如果需要
在序列暂停时触发声音,请将其设置为true。否则请不要这样做,因为它会增加
能耗。
创建音轨
sequence.createTracks(instruments).then((tracks) {
setState(() {
this.tracks = tracks;
...
});
});
createTracks返回Future<List<Track>>。您可能希望将它完成的值存储在
您的widget的状态中。
在音轨上安排事件
track.addNote(noteNumber: 60, velocity: 0.7, startBeat: 0.0, durationBeats: 2.0);
这将把中央C(MIDI音符编号60)添加到序列中,从节拍0开始,并在
2拍后停止。
track.addVolumeChange(volume: 0.75, beat: 2.0);
这将安排一个音量变化。它可以用于音量自动化。请注意,音轨音量
是线性的,而不是对数的。您可能需要使用对数刻度并转换为
线性。
控制播放
sequence.play();
sequence.pause();
sequence.stop();
启动、暂停或停止序列。
sequence.setBeat(double beat);
以节拍为单位设置序列中的播放位置。
sequence.setEndBeat(double beat);
以节拍为单位设置序列的长度。
sequence.setTempo(120.0);
以每分钟节拍数设置节奏。
sequence.setLoop(double loopStartBeat, double loopEndBeat);
启用循环。
sequence.unsetLoop();
禁用循环。
获取序列的实时信息
您可以使用SingleTickerProviderStateMixin在每一帧上调用这些方法。
sequence.getPosition(); // double
获取序列的播放位置(以节拍为单位)。
sequence.getIsPlaying(); // bool
获取序列是否正在播放。如果已到达末尾,它可能已停止。
track.getVolume();
获取音轨的音量。VolumeEvent在播放过程中可能已更改。
实时播放
track.startNoteNow(noteNumber: 60, velocity: 0.75);
立即向音轨发送MIDI Note On消息。
track.stopNoteNow(noteNumber: 60);
立即向音轨发送MIDI Note Off消息。
track.changeVolumeNow(volume: 0.5);
立即更改音轨的音量。请注意,这是线性增益,而不是对数。
工作原理
Android和iOS后端启动它们各自的音频引擎。iOS引擎将一个AudioUnit
添加到每个音轨,然后将其连接到Mixer AudioUnit。Android引擎则需要
手动完成所有渲染。它们都共享一个“BaseScheduler”,可以在
ios目录中找到。
后端不了解序列、序列中的位置、循环状态,甚至时间
(以秒或节拍为单位)。它只维护一个音轨映射,并在音频引擎
渲染完适当数量的帧时触发这些音轨上的事件。BaseScheduler
为每个音轨提供一个Buffer,其中包含其计划的事件。Buffer被认为是
线程安全的(一个读取器和一个写入器)且实时安全的(即它不会分配内存,
因此可以在音频渲染线程上使用)。
Sequence存在于Dart前端。Sequence包含Tracks。每个Track由
后端的一个Buffer支持。当您向音轨添加音符或音量更改时,它会根据
节奏和采样率,在适当的帧上将事件安排到Buffer中。
Buffer可能不够大以容纳所有事件。此外,当启用循环时,事件
将无限发生,因此Buffer永远不够大。为了解决这个问题,前端
将定期“填充”每个音轨的Buffer。
开发说明
请注意,Android构建使用了几个第三方库。Gradle构建将从GitHub
下载它们到android/third_party目录。
iOS构建依赖于AudioKit库。它将由CocoaPods下载。
要在Mac OS上构建C++测试,请进入cpp_test目录,然后运行
cmake .
make
然后,要运行测试,
./build/sequencer_test
我没有在Windows或Linux上尝试过,但它应该可以在没有太多更改的情况下工作。
待办事项
欢迎PR!如果您在项目中使用了此插件,请考虑通过修复错误或处理
这些待办事项来做出贡献。
难度:简单
- (重要)启用设置AudioKit Sampler的包络参数
- 将position_frame_t更改为64位整数以避免溢出错误
- 使常量可配置(TOP_OFF_PERIOD_MS和LEAD_FRAMES)
- 支持联合插件
难度:中等
- 开始使用Dart空安全
- 支持节奏自动化
- 支持音高弯音和自定义调音
- MIDI Out乐器
- 创建一个本身不发声,只将MIDI
事件发送到指定MIDI输出端口的乐器。
- 创建一个本身不发声,只将MIDI
- 重构
- GlobalState、Sequence和Track应该有清晰的职责划分
- Engine、Sequencer、Mixer、Scheduler也是如此
- “Track index”和“Track ID”被混用,“Track ID”应该在所有地方使用
- 使不同文件(Plugin/Engine/Scheduler)的名称和组织保持一致
- 支持MacOS
- 大部分iOS代码应该可以被重用
- 添加更多C++测试
- 特别是对于BaseScheduler。
- 添加Dart测试
- 测试循环边缘情况
- 确保没有内存泄漏或并发问题
难度:困难
- 通用C++乐器
- 创建一个可以在iOS和Android上使用的乐器。AudioKit Sampler就是这样工作的,
但这个库将其用于AudioKit的AudioUnit包装器。难点在于创建一个
C++IInstrument的通用AudioUnit包装器。
- 创建一个可以在iOS和Android上使用的乐器。AudioKit Sampler就是这样工作的,
- 完整的SFZ支持
- 这可能需要 sfizz 库
- 在Android和iOS上构建sfizz及其依赖项可能会很困难。理想情况下,它
应该具有更模块化的构建——此项目不需要其对AU/LV/VST、
音频输出和音频文件解码的支持。
- 在Android和iOS上构建sfizz及其依赖项可能会很困难。理想情况下,它
- 这可能需要 sfizz 库
- 支持Windows
- Android目录中的一些代码可能会被重用
- 录制音频输出
- 支持React Native?
- 可以使用dart2js
难度:非常困难
- (重要)音频图
- 创建音轨和效果的图,其中任何输出都可以连接到任何输入。
- 到那时,大部分音频引擎都可以跨平台。在iOS上,它可以
被包装在一个AudioUnit中,并且外部AudioUnit乐器和效果可以通过
输入和输出总线与其连接。 - LabSound 似乎是需要使用的库
- 它基于WebAudio,所以也可以有一个Web后端
- 可用于添加一些关键效果,如混响、延迟和压缩
- 支持音频输入、将输出写入文件以及其他DAW功能
- 支持Web