Rings provides a compact and expressive language for defining multimedia generation pipelines, which use conventional digital signal processing as well as machine learning, that can be compiled at runtime to target heterogeneous compute devices both locally and in the cloud. Rings is essentially a kind of headless Digital Audio Workstation (DAW) and rendering engine for visual and sonic media that can be used either as a framework for experimenting and creating generative art directly or as a tool to build interactive multimedia applications.
This project is very much a work in progress (contributions are welcome), and since the documentation is very limited its recommended that you get in touch with us if you want to use it so that you can get the support that you need one on one. Contact information is found at the bottom of this document.
Add Maven Repository:
<repositories>
<repository>
<id>flowtree</id>
<name>Almost Realism Flowtree/name>
<url>https://maven.pkg.github.com/almostrealism/flowtree</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>rings</id>
<name>Almost Realism Rings/name>
<url>https://maven.pkg.github.com/almostrealism/rings</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
Add ar-rings dependency:
<dependency>
<groupId>org.almostrealism</groupId>
<artifactId>ar-rings</artifactId>
<version>0.42</version>
</dependency>
The basic building block that features of Rings are built on the Cell. This is an interface (provided by Almost Realism Common) which represents a signal processing stage that can perform arbitrary computation and can be connected to other Cells to form a signal processing graph. This concept can be leveraged in many ways, but the way it is used in Rings specifically is to generate media. To allow this media to be structured temporally, Cell implementations which have time-varying state can additionally implement the Temporal interface.
There are a few ways to approach using Rings. These are, broadly:
- Use built in capabilities of CellFeatures, and custom implementations of the Cell and/or Temporal interfaces to create a media generation process directly.
- Use provided abstractions like PatternNote and PatternElement to define compositions in a way that is more akin to writing or arranging music and then use Rings to render those compositions.
- Use the AudioScene wrapper to define a system for generating compositions and routing them in a multichannel system akin to a Digital Audio Workstation.
These are not mutually exclusive, and can be combined in fairly arbitrary ways.
Load audio and apply a high pass filter.
publicclassMyMultimediaPipelineimplementsCellFeatures {
publicstaticvoidmain(String[] args) {
newMyMultimediaPipeline().filter();
}
publicvoidfilter() {
Supplier<Runnable> r =
// Load the samplew("Library/Snare Gold 1.wav")
// Direct the audio to a high pass filter
.f(i -> hp(2000, 0.1))
// Direct the output to a file
.o(i -> newFile("results/filter-delay-cell.wav"))
// Create a pipeline that will generate 6 seconds of audio
.sec(6);
// Compile and run the media pipeline// (this will make a best effort at// hardware acceleration, including// using the GPU if available)r.get().run();
}
}A simple example of how to define, render, and save a pattern.
publicclassMyMultimediaPipelineimplementsCellFeatures, SamplingFeatures, PatternFeatures {
publicstaticvoidmain(String[] args) {
newMyMultimediaPipeline().sineAndSnare();
}
publicvoidsineAndSnare() {
// Define the shared parameters, including how notes should be// tuned and a root for the scale and the synthdoubleduration = 8.0;
KeyboardTuningtuning = newDefaultKeyboardTuning();
WesternChromaticroot = WesternChromatic.C3;
// Settings for the synth notedoubleamp = 0.25;
intframes = (int) (2.0 * sampleRate);
// Source for the synth noteStatelessSourcesine = (params, frequency) -> sampling(sampleRate, () -> {
CollectionProducer<PackedCollection<?>> f =
multiply(c(tuning.getTone(root).asHertz()), frequency);
CollectionProducer<PackedCollection<?>> t =
integers(0, frames).divide(sampleRate);
returnsin(t.multiply(2 * Math.PI).multiply(f)).multiply(amp);
});
// Define the synth noteStatelessSourceNoteAudioaudio =
newStatelessSourceNoteAudio(sine, root, 2.0);
PatternNotesineNote = newPatternNote(List.of(audio));
sineNote.setTuning(tuning);
// Define a sampler note that will use the parameter 0.5// to choose which source to voicePatternNotechoiceNote = newPatternNote(0.5);
choiceNote.setTuning(tuning);
// Setup context for rendering the audio, including the scale,// the way to translate positions into audio frames, and the// destination for the audioAudioSceneContextsceneContext = newAudioSceneContext();
sceneContext.setFrameForPosition(pos -> (int) (pos * sampleRate));
sceneContext.setScaleForPosition(pos -> WesternScales.major(root, 1));
sceneContext.setDestination(newPackedCollection<>((int) (duration * sampleRate)));
// Setup context for voicing the notes, including the library// of samples to use (choiceNote will select from those)NoteAudioContextaudioContext = newNoteAudioContext();
audioContext.setNextNotePosition(pos -> duration);
audioContext.setAudioSelection((choice) ->
NoteAudioProvider.create("Library/Snare Gold 1.wav",
WesternChromatic.D3, tuning));
// Create the elements of the composition, leveraging// the notes that have been defined in multiple places// to create a pattern of 4 elementsList<PatternElement> elements = newArrayList<>();
elements.add(newPatternElement(sineNote, 0.0));
elements.add(newPatternElement(choiceNote, 2.5));
elements.add(newPatternElement(sineNote, 4.0));
elements.add(newPatternElement(choiceNote, 6.5));
// Adjust the position on the major scale for each of the// elements in the compositionelements.get(0).setScalePosition(List.of(0.0));
elements.get(1).setScalePosition(List.of(0.3));
elements.get(2).setScalePosition(List.of(0.5));
elements.get(3).setScalePosition(List.of(0.5));
// Render the compositionrender(sceneContext, audioContext, elements, true, 0.0);
// Save the composition to a filenewWaveData(sceneContext.getDestination(), sampleRate)
.save(newFile("results/sine-and-snare.wav"));
}
}You can also create an AudioScene to generate audio in a more structured way.
publicclassMyMultimediaPipeline {
publicstaticvoidmain(String[] args) {
newMyMultimediaPipeline().runScene();
}
publicvoidrunScene() {
// Settings for the scenedoublebpm = 120.0;
intsourceCount = 4;
intdelayLayerCount = 3;
intsampleRate = 44100;
// Create the sceneAudioScenescene = newAudioScene<>(bpm, sourceCount, delayLayerCount, sampleRate);
// Load a library of material to use for creating notes to use// in the patterns that make up the arrangementscene.setLibrary(newAudioLibrary(newFile("/Users/michael/Music/Samples"), sampleRate));
// Create a random parameterization of the sceneProjectedGenomerandom = scene.getGenome().random();
scene.assignGenome(random);
// Create a destination for the output audioWaveOutputoutput = newWaveOutput(() -> newFile("scene.wav"), 24, sampleRate, -1, false);
// Generate the media pipelineSupplier<Runnable> process = scene.runner(newMultiChannelAudioOutput(output)).iter(30 * sampleRate);
// Compile and run the pipelineprocess.get().run();
// Save the resulting audiooutput.write().get().run();
}
}Copyright 2024 Michael Murray
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Michael Murray - @ashesfall - michael@almostrealism.com
Original Project Link: https://github.com/almostrealism/rings