#音乐理论 #音乐 #理论 #作曲 #音乐性的 #作曲家 #实用工具

redact-composer-musical

redact-composer(可能也可以独立使用)封装的音乐理论领域模型和实用工具

12个版本

0.3.4 2024年4月28日
0.3.3 2024年4月19日
0.2.1 2024年3月9日
0.1.4 2024年1月19日

#295 in 音频


用于 2 箱子

MIT 许可证

145KB
3K 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元素只是抽象概念,需要产生具体的东西。这是通过另一个用于PlayChordsRenderer来实现的

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

创建作曲家

本质上,一个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();

连接到您最喜欢的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 相关 ElementComposition 的 MIDI 转换器的 midi 模块。

serde 默认

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

依赖关系

~245–770KB
~14K SLoC