# ![icon] RedACT Composer [![docs-badge]][docs-link] [![crates.io-badge]][crates.io-link] [![ci-badge]][ci-link] ![license-badge] **A Rust library for building modular musical composers.** Composers are built by creating a set of composition elements, and defining how each of these elements will generate further sub-elements. In this library's domain, these correspond to the [`Element`](crate::Element) and [`Renderer`](crate::Renderer) traits respectively. > This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Most importantly at this time > would be [spec item #4](https://semver.org/spec/v2.0.0.html#spec-item-4).
Jump to: [ [Setup](#setup) | [Example](#example) | [Bigger Example](#much-bigger-example) | [Inspector](#inspector) | [Feature Flags](#feature-flags) ]

## Setup ```shell cargo add redact-composer ``` If using the `serde` [feature](#feature-flags), `typetag` is also required: ```shell cargo add typetag ```
## Example The basic capabilities can be demonstrated by creating a simple I-IV-V-I chord composer. The full code example is located at [`redact-composer/examples/simple.rs`](https://github.com/dousto/redact-composer/blob/main/redact-composer/examples/simple.rs). ### Building Blocks This example composer will use some library-provided elements ([`Chord`](crate::musical::elements::Chord), [`Part`](crate::elements::Part), [`PlayNote`](crate::elements::PlayNote)) and two new elements: ```rust #[derive(Element, Serialize, Deserialize, Debug)] pub struct CompositionRoot; #[derive(Element, Serialize, Deserialize, Debug)] struct PlayChords; ``` Before moving ahead, some background: A composition is an n-ary tree structure and is "composed" by starting with a root [`Element`](crate::Element), and calling its associated [`Renderer`](crate::Renderer) which generates additional [`Element`](crate::Element)s as children. These children then have their [`Renderer`](crate::Renderer)s called, and this process continues until tree leaves are reached (i.e. elements that do not generate further children). This composer will use the `CompositionRoot` element as a root. Defining a [`Renderer`](crate::Renderer) for this then looks like: ```rust struct CompositionRenderer; impl Renderer for CompositionRenderer { type Element = CompositionRoot; fn render( &self, composition: SegmentRef, context: CompositionContext, ) -> Result> { 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(), ) } } ``` > Note: [`Part::instrument(...)`](crate::elements::Part::instrument) is just a wrapper for another element, indicating > that notes generated within the wrapped element are to be played by a single instrument at a time. This [`Renderer`](crate::Renderer) takes a `CompositionRoot` element (via a [`SegmentRef`](crate::SegmentRef)) and generates several children including [`Chord`](crate::musical::elements::Chord) elements (with a [`Rhythm`](crate::musical::rhythm::Rhythm) of one every two beats over the composition), and newly defined `PlayChords` element. These children are returned as [`Segment`](crate::Segment)s, which defines where they are located in the composition's timeline. At this stage, the [`Chord`](crate::musical::elements::Chord) and `PlayChords` elements are just abstract concepts however, and need to produce something concrete. This is done with another [`Renderer`](crate::Renderer) for `PlayChords`: ```rust struct PlayChordsRenderer; impl Renderer for PlayChordsRenderer { type Element = PlayChords; fn render( &self, play_chords: SegmentRef, context: CompositionContext, ) -> Result> { // `CompositionContext` enables finding previously rendered elements let chord_segments = context.find::() .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::>() }) .collect(); Ok(notes) } } ``` Here, [`CompositionContext`](crate::render::context::CompositionContext) is used to reference the previously created [`Chord`](crate::musical::elements::Chord) segments. Then the [`Note`](crate::musical::elements::Note)s from each [`Chord`](crate::musical::elements::Chord) within an octave range are [`play`](crate::musical::Note::play)ed over the [`Chord`](crate::musical::elements::Chord) segment's timing. ### Creating the Composer In essence, a [`Composer`](crate::Composer) is just a set of [`Renderer`](crate::Renderer)s, and can be constructed with just a little bit of glue: ```rust let composer = Composer::from( RenderEngine::new() + CompositionRenderer + PlayChordsRenderer, ); ``` And finally the magic unfolds by passing a root [`Segment`](crate::Segment) to its [`compose()`](crate::Composer::compose) method. ```rust // 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(); ``` > Note: [`SF2Synthesizer`](crate::synthesis::SF2Synthesizer) does not have any default/embedded SoundFont so you'll have > to supply your own. (FluidR3, created by Frank Wen, is a great general-purpose, high-quality, MIT licensed option) The output should sound somewhat like this: Additionally, composition outputs support serialization/deserialization (with `serde` feature, enabled by default). ```rust // Write the composition output in json format fs::write("./composition.json", serde_json::to_string_pretty(&composition).unwrap()).unwrap(); ```
## Much bigger example Check out [this repo](https://github.com/dousto/redact-renderer-example) for a more in depth example which utilizes additional features to create a full length composition.
## Inspector Debugging composition outputs can quickly get unwieldy with larger compositions. [redact-composer-inspector](https://dousto.github.io/redact-composer-inspector/) is a simple web tool that helps to visualize and navigate the structure of [`Composition`](crate::Composition) outputs (currently only compatible with json output). For example, here is the [simple example loaded in the inspector](https://dousto.github.io/redact-composer-inspector/inspect?composition=examples/simple).
## Feature Flags ### `default` `derive`, `musical`, `midi`, `synthesis`, `serde` ### `derive` default Enable derive macro for [`Element`](crate::Element). ### `musical` default Include [`musical`](crate::musical) domain module. ([`Key`](crate::musical::Key), [`Chord`](crate::musical::Chord), [`Rhythm`](crate::musical::rhythm::Rhythm), etc..). ### `midi` default Include [`midi`](crate::midi) module containing MIDI-related [`Element`](crate::Element)s and MIDI converter for [`Composition`](crate::Composition)s. ### `synthesis` default Include [`synthesis`](crate::synthesis) module to synthesize [`Composition`](crate::Composition)s into audio. ### `serde` default Enables serialization and deserialization of [`Composition`](crate::Composition) outputs via (as you may have guessed) [`serde`](https://docs.rs/serde/latest/serde/).
[icon]: https://dousto.github.io/redact-composer-inspector-dev/favicon-32.png "" [docs-badge]: https://docs.rs/redact-composer/badge.svg [docs-link]: https://docs.rs/redact-composer [crates.io-badge]: https://img.shields.io/crates/v/redact-composer.svg [crates.io-link]: https://crates.io/crates/redact-composer [ci-badge]: https://github.com/dousto/redact-composer/workflows/CI/badge.svg [ci-link]: https://github.com/dousto/redact-composer/actions [license-badge]: https://img.shields.io/crates/l/redact-composer.svg