#element #composer #redact-composer #macro #musical #modular #composers

redact-composer-derive

为redact-composer提供派生宏

3个版本

0.1.2 2024年4月28日
0.1.1 2024年3月9日
0.1.0 2024年1月14日

#16 in #composer

每月 32 次下载
用于 5 个crate(其中2个直接使用)

MIT 许可证

15KB
55

图标 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 元素只是抽象概念,需要产生具体的东西。这是通过为 PlayChords 创建另一个 Renderer 来实现的。

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 的音符将在 Chord 段落的时序上 play

创建作曲家

本质上,一个 Composer 只是一组 Renderer,只需一点粘合剂即可构建。

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

最后,通过将其 compose() 方法传递一个根 Segment 来施展魔法。

// 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();

当连接到您最喜欢的MIDI播放器时,composition.mid 文件应该听起来像这样

https://github.com/dousto/redact-composer/assets/5882189/9928539f-2e15-4049-96ad-f536784ee7a1

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

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

更大的示例

查看 这个存储库 以获取更深入的示例,该示例利用了额外的功能来创建完整的曲子。

检查器

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

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

功能标志

默认

derivemusicalmidiserde

derive 默认

Element 启用 derive 宏。

musical 默认

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

midi 默认

包含 midi 模块,其中包含与 MIDI 相关的 Element 和用于 Composition 的 MIDI 转换器。

serde 默认

启用通过 (如你所猜测) serdeComposition 输出的序列化和反序列化。

依赖项

~0.6–1.2MB
~26K SLoC