RedACT Composer
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 and Renderer traits respectively.
This project adheres to Semantic Versioning. Most importantly at this time would be spec item #4.
Jump to: [ Setup | Example | Bigger Example | Inspector | Feature Flags ]
Setup
cargo add redact-composer
If using the serde feature, typetag is also required:
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.
Building Blocks
This example composer will use some library-provided elements (Chord,
Part, PlayNote) and two new elements:
;
;
Before moving ahead, some background: A composition is an n-ary tree structure and is "composed" by starting with a
root Element, and calling its associated Renderer which
generates additional Elements as children. These children then have their
Renderers 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 for this
then looks like:
;
Note:
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 takes a CompositionRoot element (via a SegmentRef) and generates several
children including Chord elements (with a Rhythm of one every two beats over the composition), and
newly defined PlayChords element. These children are returned as Segments, which defines where they
are located in the composition's timeline.
At this stage, the Chord and PlayChords elements are just abstract concepts
however, and need to produce something concrete. This is done with another Renderer for
PlayChords:
;
Here, CompositionContext is used to reference the previously created
Chord segments. Then the Notes from each
Chord within an octave range are played over the
Chord segment's timing.
Creating the Composer
In essence, a Composer is just a set of Renderers, and can be constructed with
just a little bit of glue:
let composer = from;
And finally the magic unfolds by passing a root Segment to its
compose() method.
// Create a 16-beat length composition
let composition_length = composer.options.ticks_per_beat * 16;
let composition = composer.compose;
// Convert it to a MIDI file and save it
convert.save.unwrap;
// And/or synthesize it to audio with a SoundFont
let synth = new.unwrap;
synth.synthesize.to_file.unwrap;
Note:
SF2Synthesizerdoes 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:
https://github.com/dousto/redact-composer/assets/5882189/aeed4e7a-5543-4cf1-839d-d5f62c55fea9
Additionally, composition outputs support serialization/deserialization (with serde feature, enabled by default).
// Write the composition output in json format
write.unwrap;
Much bigger example
Check out this repo 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 is a simple web tool that helps to
visualize and navigate the structure of Composition outputs (currently only compatible with
json output).
For example, here is the simple example loaded in the inspector.
Feature Flags
default
derive, musical, midi, synthesis, serde
derive default
Enable derive macro for Element.
musical default
Include musical domain module. (Key, Chord,
Rhythm, etc..).
midi default
Include midi module containing MIDI-related Elements and MIDI converter for
Compositions.
synthesis default
Include synthesis module to synthesize Compositions into audio.
serde default
Enables serialization and deserialization of Composition outputs via (as you may have guessed)
serde.