Skip to content
broadyaPublic

About

Beautiful sounds come from the clouds.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

116 Commits

Folders and files

Repository files navigation

RAINBOHz

RAINBOHz

Project Summary

RAINBOHz is a research project exploring additive synthesis at large scale, referred to as macro-scale additive synthesis. It operates at the "macro-scale" from two perspectives. It allows large numbers of additive partials to be used, and it allows control over the macro timescale i.e. over a complete musical work (or a significant part thereof) defined by a score.

Scores are written in a domain-specific, declarative language called HzL. This language and its compiler are part of this project. It is expected that practitioners will use a combination of hand-coding, assembly from pre-built component libraries, and LLMs to generate HzL scores.

Some key concepts and goals of the project are listed below. Only a limited number of these goals have been implemented to date.

From an audio / music perspective:

  • Provide an interactive environment for sound design, composition and live coding.
  • Support authoring based on a language for describing scores.
  • Scale to large numbers of additive partials.
  • Support polyrhythms / polytempo.
  • Support spatial audio.
  • Allow manipulation of scores at different levels of abstraction.
  • Integrate with DAWs for workflow.
  • Do not provide a playable "instrument".

From a computer science perspective:

  • Provide a domain-specific, declarative language describing a score.
  • Allow composition with reusable components and version control through git.
  • Support deployment on single-user workstations, LANs, and public cloud platforms.
  • Render large numbers of partials with acceptable latency making use of vertical and horizontal scalability.
  • Provide rapid rendering and random-access playback based on mathematical integration of state.

The project is currently in an early stage, moving from a proof-of-concept to the target architecture. The current version should be considered as an emerging research project. Much testing, validation and additional features still remain to be added. Here be dragons. It's not only that "your mileage may vary", it's that the wheels might fall off, or pressing the brake pedal might make the doors open. You have been warned!

In particular the following are large gaps in the current implementation.

  • Anti-aliasing
  • API / network approach (currently some parts are implemented)
  • Horizontal scalability
  • Build using GitHub actions

The architecture takes all of these issues into account, but they are simply not yet implemented.

Audio Warning

Turn down your headphone / speaker level before trying experiments with RAINBOHz. This software can generate unusual audio signals that could damage your speakers or headphones, and more importantly could damage your hearing. Keep those levels down whilst experimenting!

AI Disclosure

This project was initiated prior to the widespread use of LLMs for code generation. The first proof-of-concept was written following a largely conventional approach.

The current source code and most of the documentation is primarily AI-generated. The use of LLMs has accelerated development of RAINBOHz, but in its current form not all errors have been found, algorithms may be suboptimal, and code style may be inappropriate. As the project matures, code reviews and refactoring will gradually improve this situation.

Some key artefacts such as the graph database schema, and most of the core HzL language have been coded by hand.

Architecture

RAINBOHz is designed so that it can be deployed and scaled using a distributed architecture. This has not yet been demonstrated. It is a design concept - albeit one that has been the subject of considerable effort, supported by performance and capacity calculations. The current codebase includes some of the initial components that will make this possible, such as an early version of one of the REST APIs.

RAINBOHz can be deployed on a single computing node, which may be sufficient depending upon the required throughput and the acceptable use of local resources. In its current state, this is the only possible deployment model.

Horizontal scalability is based on sharding of partials over multiple nodes. The sharding is in turn based on dividing the domain-specific language source over the nodes. The exact mechanism for this is not yet designed, but there are some clear approaches such as division by sound entity.

This can be performed by placing all HzL code in a git repository, then using partial clones to divide the model between the nodes. It is likely that the sharding approach will evolve from there.

Each processing node is an independent end-to-end synthesis node. Each node performs the full end-to-end process of compiling HzL to the graph database model, performing the database transformations, performing precomputation, and finally rendering frames of audio. When deployed on a distributed system, this audio may then be written to object storage. A more performant option may be to stream the audio to the next stage in the pipeline.

The next stage makes audio available to clients. It performs summing and anti-aliasing, then makes audio available to clients via a REST API.

The pipeline can be viewed as a chain of components as follows:

Compilation — essentially a software build system.

  • The Hertz Language, HzL
  • Hertz Language Compiler
  • Hertz Language Build System
  • canonical-form — the compiled, loadable score

Rendering — transformation of the score specification to audio

  • rendering-engine — loads, transforms, precomputes and renders
  • engine-db — the one graph database, hosted in process, sharded by partial set
  • Object storage — WAV objects addressed by URL

Playback — merging the shards on demand.

  • playback-server — sums per-shard objects and serves audio
  • Playback Component
  • Playback Plug-in

Orchestration — a message bus connecting the components. Kafka is the current target. This exists currently only as a design idea.

See architecture/overview.md for the full description, and §11 there for why an earlier pipelined design was collapsed into this one.

Programming Languages

C++20 using clang / Coding conventions according the C++ Core Guidelines. Python 3.12

Target Platforms

This application is intended as a server-side application running on UNIX-like operating systems and/or cloud computing platforms.

The primary goal is to target cloud platforms. This is likely to target AWS Graviton. Other platforms are also under consideration. This will demonstrate horizontal scalability, and will also leverage SIMD and similar to optimize vertical scalability. There has been some consideration as to the use of GPUs, but the current viewpoint is that the ARM architecture will offer the best cost / performance tradeoff and is easier to work with.

Project Status

This project is currently moving from its proof-of-concept phase towards a scalable architecture.

Repository Layout

Directory Contents
architecture/ Target architecture: overview and diagrams
hzl-compiler/ HzL compiler, Python — first version: HzL to Cypher for engine-db
engine-db/ Schema of engine-db, the engine's database and the only one — under construction
canonical-db/ Canonical (practitioner-facing) model — design artefact, not the implementation; out of date
rendering-engine/ Rendering engine, C++ — loads, transforms, precomputes and renders; first version
playback-client/ Playback client, C++ — loops a range of a score through the default audio device; first version
docs/adr/ Architecture Decision Records
cmake/ Shared CMake configuration for the C++ components: warnings, presets and cross toolchains
scripts/ Schema loading, validation, diagrams and comment wrapping, the shared DDL machinery, the native model's mapping check, and the score-forms check
proof-of-concept/ Proofs of concept, each in its own sub-directory, tried before being built into the components. The original sub-directories are frozen as a historical record. Not built
playground/ Musical experiments: pg loops an HzL piece through the engine and reloads it on request. No stability guarantee; may change or vanish at any time

Developer Guide

The developer guide is currently only in place for macOS, but it should be possible to follow similar steps on other operating systems.

Build

Everything is driven from the root Makefile. make help lists the targets.

make docs                       # regenerate this README, schema diagrams and API docs
make readme                     # just this README, from docs/readme/
make validate-schemas           # check the schemas load into a real Ladybug
make check-schema-comments      # show schema comments that need rewrapping
make check-engine-db-mapping    # check the native engine-db's types against the schema
make init-engine-db             # build a database from the engine schema
make rendering-engine        # build the rendering engine
make test-rendering-engine   # and run its tests
make hzl-compile             # compile an HzL score to Cypher (hzl-compiler/)
make run-hzl-score           # ...and render it with the engine
make test-hzl-compiler       # the compiler's tests
make lint-hzl-compiler       # the compiler's lint, format and type checks
make lock                    # re-lock Python dependencies after changing them (needs uv)
make audit-python            # check the locked Python packages for known vulnerabilities

Python packages are installed into the root .venv from a hash-checked lock, requirements-dev.txt, which make lock generates from requirements-dev.in and hzl-compiler/pyproject.toml. Edit those, run make lock, and commit the lock with them; never edit the lock by hand. See ADR 0021.

The C++ components are built with CMake behind those targets, and using CMake directly works just as well — see rendering-engine/README.md. The first C++ build compiles Ladybug, and Apache Arrow's Parquet reader, from source and takes a long time.

make test runs schema validation first, because it takes seconds and needs no compiler — a schema that does not load makes everything after it moot.

Databases

The implementation uses one physical database, engine-db, hosted in process by rendering-engine. It is the engine's whole database: everything in the canonical model, plus everything the engine needs in order to determine dependencies, precompute and render. Its schema is engine-db/schema/engine-schema.cypher, and it is under construction — it holds the rendering-model and canonical-model concepts, but not yet the modulator musical timelines or any dependency information. See ADR 0006.

engine-db has two implementations of that one schema, and a run uses one of them (ADR 0028, proposed): the Ladybug graph database, and a native in-memory model whose hand-written types are tagged with the part of the schema each holds. hzlc's tables load natively, and much faster; a .cypher score and --validate-db need Ladybug. make check-engine-db-mapping fails when the schema and the native types' tags disagree, so a schema edit is followed by a look at the native side. See rendering-engine/README.md.

The .cypher schema file under canonical-db/ is a design artefact. It describes the practitioner-facing model and is kept loadable, but nothing loads it; it is a source engine-db draws on, not something the system runs on, and it is now considerably out of date beside engine-db.

Keeping both loadable is worth it: a model that a real database rejects is a model with a mistake in it, and make validate-schemas catches that in seconds — run it to see where the two stand. The databases themselves are disposable, gitignored, and rebuild in a few seconds.

make init-engine-db             # apply the schema (safe to repeat)
make reset-engine-db            # destroy and recreate from schema

Each directory also carries init_db.sh and reset_db.sh for use outside make.

Never pipe a .cypher file into lbug directly. It reports errors on stdout, exits 0 regardless, and silently skips statements when fed a whole file — so schemas half-load with no diagnostic at all. Use the targets above, which wrap scripts/load_schema.py. See ADR 0002.

Prerequisites

xcode-select --install
brew install cmake ninja ccache llvm ladybug

Homebrew's llvm is keg-only and stays off PATH deliberately; it supplies clang-tidy, which Apple's clang does not ship. Python 3.12+ is needed for the schema tooling. brew install uv is needed only to change Python dependencies (make lock).

Ladybug

The project makes extensive use of graph databases, using Ladybug.

There are two separate things here, and the distinction matters:

  • The Homebrew package (brew install ladybug) provides the interactive shell, for inspecting databases by hand. Convenient, not required.
  • The embedded library is compiled from pinned source as part of the C++ build. This is what the components actually link against. See ADR 0001.

Keep the two at the same version. The storage format changes between releases, so a shell older than the linked library may not open a database the library wrote. brew outdated ladybug against RE_LADYBUG_GIT_TAG in rendering-engine/cmake/Dependencies.cmake is the check.

Tools

Schema Visualisation

scripts/ladybug_schema_to_mermaid.py generates a Mermaid UML class diagram from a LadybugDB DDL schema file.

python3 scripts/ladybug_schema_to_mermaid.py engine-db/schema/engine-schema.cypher --markdown > schema-diagram.md

Open schema-diagram.md in VS Code (⌘⇧V) or paste the output into mermaid.live to view the rendered diagram. make docs writes the repository's own diagrams to engine-db/docs/ and canonical-db/docs/.

Schema Validation

scripts/validate_ladybug_schema.py checks a schema by executing it against a throwaway LadybugDB, so "valid" means what the database actually accepts rather than what a reimplemented parser believes. Failures are reported with their source line range and the exact database error.

python3 scripts/validate_ladybug_schema.py engine-db/schema/engine-schema.cypher --verbose

Normally reached via make validate-schemas. The statement splitting and error detection are shared with the loader, in scripts/ladybug_ddl.py.

Comment Wrapping

scripts/wrap_cypher_comments.py refills the full-line // comments in Cypher DDL to 64 columns, never hyphenating: a word that crosses the limit stays whole. Only comments change. Trailing comments, /* ... */ blocks, tables, -> paths, aligned columns, list items and NAME = value lines keep their shape; the docstring lists the rules.

python3 scripts/wrap_cypher_comments.py --check engine-db/schema/engine-schema.cypher

make check-schema-comments shows the diff for both schemas, and make format-schema-comments rewrites them in place.

About

Beautiful sounds come from the clouds.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages