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个直接使用)
15KB
55 行
RedACT Composer
一个用于构建模块化音乐作曲家的Rust库。
通过创建一组作曲元素,并定义每个元素如何生成进一步子元素来构建作曲家。在这个库的领域中,这些分别对应于Element
和Renderer
特质。
示例
基本功能可以通过创建一个简单的I-IV-V-I和弦作曲家来展示。完整的代码示例位于redact-composer/examples/simple.rs
。
构建模块
此示例作曲家将使用一些库提供的元素(Chord
、Part
、PlayNote
)和两个新元素
#[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
返回,它们定义了它们在曲子时间线中的位置。
然而,在这个阶段,Chord
和 PlayChords
元素只是抽象概念,需要产生具体的东西。这是通过为 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输出)。
例如,这里是在检查器中加载的 简单示例。
功能标志
默认
derive
,musical
,midi
,serde
derive
默认
为 Element
启用 derive 宏。
musical
默认
包含 musical
领域模块。 (Key
,Chord
,Rhythm
等..)。
midi
默认
包含 midi
模块,其中包含与 MIDI 相关的 Element
和用于 Composition
的 MIDI 转换器。
serde
默认
启用通过 (如你所猜测) serde
对 Composition
输出的序列化和反序列化。
依赖项
~0.6–1.2MB
~26K SLoC