#render #synth #convert #redact-composer #synthesize #encoding

redact-composer-synthesis

redact-composer的音频合成工具

2个版本

0.1.1 2024年4月28日
0.1.0 2024年4月28日

#163 in 音频

Download history 220/week @ 2024-04-22 117/week @ 2024-04-29 14/week @ 2024-05-20 8/week @ 2024-05-27 1/week @ 2024-06-10

每月58次下载
redact-composer中使用

MIT许可证

195KB
3.5K SLoC

图标 RedACT Composer

docs-badge crates.io-badge ci-badge license-badge

Rust库,用于构建模块化音乐作曲家。

作曲家通过创建一组作曲元素,并定义每个元素如何生成更进一步的子元素来构建。在这个库的领域中,这些分别对应于ElementRenderer特性。

该项目遵循语义化版本控制。目前最重要的是规范项#4



设置

cargo add redact-composer

如果使用serde 特性,则需要typetag

cargo add typetag

示例

基本功能可以通过创建一个简单的I-IV-V-I和弦作曲家来演示。完整的代码示例位于redact-composer/examples/simple.rs

构建块

这个示例作曲家将使用一些库提供的元素(ChordPartPlayNote)和两个新的元素

#[derive(Element, Serialize, Deserialize, Debug)]
pub struct CompositionRoot;

#[derive(Element, Serialize, Deserialize, Debug)]
struct PlayChords;

在继续前进之前,先了解一下背景:一个组合是一个n元树结构,它从根节点 Element 开始,调用其关联的 Renderer,生成额外的 Element 子节点。然后调用这些子节点的 Renderer,这个过程一直持续到到达树叶子(即不再生成子节点的元素)。

这个作曲家将使用 CompositionRoot 元素作为根。为这个元素定义一个 Renderer 看起来是这样的:

struct CompositionRenderer;
impl Renderer for CompositionRenderer {
    type Element = CompositionRoot;

    fn render(
        &self, composition: SegmentRef<CompositionRoot>, context: CompositionContext,
    ) -> Result<Vec<Segment>> {
        let chords: [Chord; 4] = [
            (C, maj).into(),
            (F, maj).into(),
            (G, maj).into(),
            (C, maj).into(),
        ];

        Ok(
            // Repeat the four chords over the composition -- one every two beats
            Rhythm::from([2 * context.beat_length()])
                .iter_over(composition)
                .zip(chords.into_iter().cycle())
                .map(|(subdivision, chord)| chord.over(subdivision))
                .chain([
                    // Also include the new component, spanning the whole composition
                    Part::instrument(PlayChords).over(composition),
                ])
                .collect(),
        )
    }
}

注意: Part::instrument(...) 只是一个其他元素的包装器,表示在包装元素内生成的音符将由单个乐器一次演奏。

这个 Renderer 接收一个 CompositionRoot 元素(通过 SegmentRef),并生成多个子元素,包括 Chord 元素(在整部作品的节奏中每两拍有一个),以及新定义的 PlayChords 元素。这些子元素作为 Segment 返回,它们定义了它们在作品时间线中的位置。

在这个阶段,ChordPlayChords 元素只是抽象概念,需要产生一些具体的东西。这通过另一个 Renderer 来完成,用于 PlayChords

struct PlayChordsRenderer;
impl Renderer for PlayChordsRenderer {
    type Element = PlayChords;

    fn render(
        &self, play_chords: SegmentRef<PlayChords>, context: CompositionContext,
    ) -> Result<Vec<Segment>> {
        // `CompositionContext` enables finding previously rendered elements
        let chord_segments = context.find::<Chord>()
            .with_timing(Within, play_chords)
            .require_all()?;
        // As well as random number generation
        let mut rng = context.rng();

        // Map Chord notes to PlayNote elements, forming a triad
        let notes = chord_segments
            .iter()
            .flat_map(|chord| {
                chord.element
                    .iter_notes_in_range(Note::from((C, 4))..Note::from((C, 5)))
                    .map(|note|
                        // Add subtle nuance striking the notes with different velocities
                        note.play(rng.gen_range(80..110) /* velocity */)
                            .over(chord))
                    .collect::<Vec<_>>()
            })
            .collect();

        Ok(notes)
    }
}

在这里,CompositionContext 用于引用之前创建的 Chord 段落。然后,在八度范围内从每个 Chord 中播放 Note,跨越 Chord 段落的节奏。

创建作曲家

本质上,一个 Composer 只是一组 Renderer,并且只需要一点粘合剂就可以构建。

let composer = Composer::from(
    RenderEngine::new() + CompositionRenderer + PlayChordsRenderer,
);

最后,通过将根 Segment 传递给其 compose() 方法,魔法就会展开。

// Create a 16-beat length composition
let composition_length = composer.options.ticks_per_beat * 16;
let composition = composer.compose(CompositionRoot.over(0..composition_length));

// Convert it to a MIDI file and save it
MidiConverter::convert(&composition).save("./composition.mid").unwrap();

// And/or synthesize it to audio with a SoundFont
let synth = SF2Synthesizer::new("./sounds/sound_font.sf2").unwrap();
synth.synthesize(&composition).to_file("./composition.wav").unwrap();

注意:SF2Synthesizer 没有任何默认/嵌入的 SoundFont,所以你需要提供自己的。 (由 Frank Wen 创建的 FluidR3 是一个很好的通用、高质量、MIT 许可的选项)

输出应该听起来像这样

https://github.com/dousto/redact-composer/assets/5882189/aeed4e7a-5543-4cf1-839d-d5f62c55fea9

此外,作品输出支持序列化/反序列化(通过启用默认的 serde 功能)。

// Write the composition output in json format
fs::write("./composition.json", serde_json::to_string_pretty(&composition).unwrap()).unwrap();

更大的例子

请查看 这个仓库 以获取更深入的示例,该示例利用其他功能创建全长作品。

检查器

对于较大的作品,调试作品输出可能会很快变得难以控制。 redact-composer-inspector 是一个简单的网络工具,有助于可视化并导航 Composition 输出的结构(目前仅与 json 输出兼容)。

例如,这里是在检查器中加载的简单示例:简单示例

功能标志

默认

derivemusicalmidisynthesisserde

derive 默认

启用针对 Element 的 derive 宏。

musical 默认

包含 musical 领域模块。(KeyChordRhythm 等..)。

midi 默认

包含包含 MIDI 相关 Element 和 MIDI 转换器 Compositionmidi 模块。

synthesis 默认

包含将 Composition 合成到音频的 synthesis 模块。

serde 默认

启用通过 (正如你可能猜到的) serdeComposition 输出的序列化和反序列化。

依赖项

~3.5MB
~66K SLoC