Onda projects
An Onda project is an .ondaproject JSON file referencing an entry source and optional buffer
assets. Its containing directory is the base and containment boundary for paths stored in the
manifest (entry and file-backed buffer bindings), but the project does not need a dedicated
directory. It is an editable, host-neutral representation: the CLI, Onda Run, and other hosts
resolve the same entry and logical buffer bindings.
Any basename is valid. Filesystem exports use the target name supplied by the user.
Create one with:
onda project my-project
onda compile my-project/my-project.ondaproject
onda run my-project/my-project.ondaproject
For complete musical uses of the format, see the checked-in project showcases: a morphable wavetable bank, a typed-buffer score driving a modal instrument, and a stereo impulse response driving a room effect. Each is self-contained and renders without additional host bindings.
The destination passed to onda project must be new or an empty directory. Onda writes the complete
project to a sibling staging directory and publishes it with one rename, so a failed export does not
leave a partially written project at the destination.
The generated layout is:
my-project/
├── my-project.ondaproject
├── assets/
└── code/
└── main.onda
Project file
The generated my-project.ondaproject starts as:
{
"entry": "code/main.onda"
}
Compile constants
constants assigns project defaults to executable-root config const declarations. Values use
ordinary JSON booleans, numbers, or arrays. Use a decimal string for an i64 value so JSON tools
cannot round it:
{
"entry": "code/main.onda",
"constants": {
"Enabled": true,
"Channels": 8,
"Seed": "9007199254740993",
"Window": [0.0, 0.5, 1.0]
}
}
The declaration in Onda source remains the type authority, and project compilation rejects
unknown, ordinary (non-configurable), or mistyped constants. Project values replace the authored
initializers for that project. An explicit host selection has higher precedence; for example,
onda compile synth.ondaproject --const Channels=2 overrides the manifest value only for that
invocation.
entry and buffer file paths are Unicode NFC-normalized, project-relative UTF-8 paths using /.
Each path component is at most 255 UTF-8 bytes. Absolute paths, ., .., empty components,
backslashes, control characters, Windows-reserved characters and device names, and components
ending in a dot or space are rejected. Referenced files must also remain distinct under portable
Unicode case folding and cannot conflict as both a file and an ancestor directory. Paths which
traverse symlinks are rejected. This keeps editable filesystem projects and live watching bound to
stable paths; immutable captured project images are unaffected.
The manifest’s containing directory is its project root. A manifest may occur at any path in a
larger file set; its entry and file-backed buffers are resolved relative to that directory. The
format assigns no meaning to directory names such as code, src, assets, or media.
That manifest containment does not restrict normal include/import resolution while editing on a
native filesystem. Source references may reach outside the manifest’s directory, so an editable
workspace is not necessarily self-contained. onda project --from and Save as project capture
the exact reachable graph, relocate its entry to code/main.onda, preserve meaningful source
subdirectories below code/, and rewrite references syntax-aware. Sources outside the capture root
are placed below code/external/. Those packaged exports are self-contained and portable to
filesystem-free hosts.
Packaging an existing source
--from captures the exact reachable source graph, relocates files outside the entry directory,
and rewrites non-standard-library imports and includes syntax-aware:
onda project portable-sampler \
--from src/sampler.onda \
--buffer sample=recording.wav \
--buffer sequence=sequence.ondabuffer
The exported code/, assets/, and code/main.onda layout is a publication convention, not a
required project shape. An .ondaproject file may instead live alongside existing repository
sources and assets, name any contained entry, and bind assets at any contained relative path. Each --buffer name must
be declared by the compiled program. Its element type and static channel
count must match the declaration. WAV inputs become canonical f32 assets; .ondabuffer preserves
any supported primitive type. The resulting directory no longer depends on the original source or
buffer paths.
For a fixed buffer array, CLI bindings address physical slots by name, such as
--buffer 'piano[39]=middle-c.wav'. Unmentioned scalar buffers and array slots remain neutral and
are not written into the project. A project therefore records available assets, not a requirement
that every declared resource be populated.
buffers maps each declared Onda buffer name to either a file, typed inline data, or an array of
those bindings. Array entries bind slots in declaration order; null leaves a slot neutral and
unbound. Unlike CLI overrides, the manifest uses the logical array name (piano), not flat keys
such as piano[0]:
{
"buffers": {
"impulse": {
"file": "assets/impulse.wav"
},
"steps": {
"inline": {
"element": "i32",
"channels": 1,
"sample_rate": 48000,
"values": [0, 4, 7, 12]
}
},
"large_ids": {
"inline": {
"element": "i64",
"channels": 1,
"sample_rate": 1,
"values": ["0", "9223372036854775807"]
}
},
"piano": [
{ "file": "assets/a0.ondabuffer" },
null,
{ "file": "assets/b0.ondabuffer" }
]
}
}
Inline values are frame-major and interleaved by channel. Their element type must exactly match
the declaration in Onda source. i64 values are decimal strings so JSON tooling cannot round
them through an inexact number representation.
The optional checked-in JSON Schema can be associated with
*.ondaproject by editor tooling, but project files do not embed a schema URL. Onda performs
stricter validation when loading them, including channel divisibility, numeric conversion,
resource limits, portable filenames, and filesystem containment.
Buffer files
Projects accept:
.ondabuffer, Onda’s canonical lossless typed buffer container forbool,i32,i64,f32, andf64.- WAV input as a convenience adapter. WAV data is decoded to an
f32buffer.
An .ondabuffer stores the element type, frames, channels, sample rate, frame-major interleaved
payload, and a SHA-256 content digest. Integer and floating-point payloads use fixed-width
little-endian encoding; booleans are exactly 0 or 1. Project images and materialized exports
use .ondabuffer, so portability does not depend on a host’s audio codecs.
Exports preserve the original asset basename when the host knows it, replacing the extension with
.ondabuffer. Assets without filename provenance use their logical buffer name. Content hashes remain
the immutable asset identity and are added to filenames only when needed to resolve a portable
filename collision.
--buffer name=path remains available for onda run play and onda run render. It overrides a
project binding with the same physical name for that invocation. A fixed-array slot uses a quoted
shell argument such as --buffer 'piano[1]=replacement.ondabuffer'.
GUI workflows
The egui and webview run hosts expose one Open Onda source or project importer accepting .onda
and .ondaproject files. Dropping either input onto the window works as well.
While an editable project is open, the native run host watches the selected manifest and its file-backed buffer assets in addition to the entry and transitive non-standard-library sources. Changing any of them reloads the project; inline assets change when the manifest changes. Filesystem-backed Onda inputs, source dependencies, and project assets must not traverse symlinks; the loader reports the offending component instead of establishing ambiguous live-watch semantics.
Once a source or project is loaded, Save as project captures the exact reachable sources and the currently bound buffers into a new portable project directory. Existing inline project assets are preserved, and file bindings selected in the host replace the corresponding packaged assets. The destination must be new or empty so publication remains atomic.
The browser playground provides Open project and Download project controls. Open project
accepts a single .onda or .on file, or a project ZIP containing one or more .ondaproject
files. It intentionally does not offer bare .ondaproject files in the browser picker: a selected
manifest alone does not grant a web page access to the sibling sources and assets that it names.
ZIP the manifest together with every referenced source and asset, then select that ZIP. When an
archive contains several projects, the playground asks which manifest to open.
For example, package the checked-in Embedded Room showcase with:
cd examples/projects/embedded_room
zip -r embedded-room.zip embedded-room.ondaproject code assets
Download project creates the same kind of portable ZIP from the current in-memory source files
and bound buffers. Buffer payloads are canonicalized as .ondabuffer, including bool, i32,
i64, f32, and f64 data. The ZIP is only a browser transport: after extraction, open its
.ondaproject file directly with the native CLI or run hosts.
Immutable project images
The onda_project crate also defines ProjectImage, an immutable checkpoint intended for DAW
state, browser tooling, and other hosts. One image contains:
- the relocated entry identity;
- project-selected configuration constants;
- the exact built-in standard-library fingerprint;
- exact source documents;
- resolved include/import edges;
- logical buffer-name to content-addressed asset bindings;
- canonical typed assets;
- a schema version and root content digest.
SourceImage::capture converts a successful frontend source manifest into a portable graph and
rewrites include/import references syntax-aware. SourceImage::replay loads that graph without
consulting the filesystem and rejects a mismatched built-in standard library.
Loading an editable project treats its entry and every unclaimed .onda or .on file as a UTF-8
source document; project manifests and file-backed buffer bindings take precedence over filename
extensions. This preserves extensionless entry files and work-in-progress sources which are not
reachable from the entry. The reachable graph must load and parse successfully; unreachable
documents are preserved verbatim and do not participate in compilation.
When a file set contains multiple manifests, they form one shared source workspace. Every valid
manifest participates in classifying manifest and buffer-asset paths, while the selected
manifest alone chooses the entry and active buffer bindings. This lets several projects share and
cross-reference source files without mistaking another project’s .onda-named asset for source.
ProjectImage::serialize produces the bounded,
versioned binary image; ProjectImage::deserialize verifies every asset and the root digest before
publication. materialization_plan returns relative filenames and bytes without writing the
filesystem, leaving atomic publication policy to the host.
The native C runtime treats assets from editable filesystem projects and immutable images as program
defaults. onda_compile_file performs source analysis and code generation before decoding external
assets, then makes those decoded assets part of the compiled program without constructing a portable
project image. onda_project_image_compile instead retains shared ownership of the image’s decoded
assets. Every instance initially binds the program-owned sample storage without copying it. A project
binding is rejected when reachable Onda code may write that physical buffer slot. Hosts can replace a
default with onda_bind_buffer, unbind it to obtain the neutral buffer behavior, or restore it with
onda_reset_buffer_to_project_default. Instances retain their compiled program and project assets,
so destroying the original program or image handle does not invalidate their bindings.
Native and web API parity
The C API and @onda-lang/wasm-compiler expose the same project operations over the same
onda_project implementation. The terminology separates an ephemeral source workspace from a
portable project image:
| Operation | C API | Web compiler |
|---|---|---|
| Compile an editable filesystem source or project | onda_compile_file |
— |
| Compile exact in-memory sources | onda_compile_source_graph |
compileWorkspace |
| Capture/build an image | onda_project_image_capture |
createProjectImage |
| Load a materialized project file set | onda_project_image_load_files |
loadProjectFiles |
| Serialize or inspect an image | onda_project_image_serialize, onda_project_image_* getters |
createProjectImage, inspectProjectImage |
| Compile an image | onda_project_image_compile |
compileProjectImage |
| Produce relative files and bytes | onda_project_image_materialize |
materializeProjectImage |
| Encode/decode typed buffers | onda_buffer_asset_encode, onda_buffer_asset_decode |
encodeBufferAsset, decodeBufferAsset |
| Query immutable format contracts | onda_project_image_format_version, onda_buffer_asset_format_version, onda_current_stdlib_digest |
projectCapabilities |
onda_compile_file is the native editable-filesystem entry point: it accepts .onda, .on, and
.ondaproject, attaches project buffers as immutable defaults, and returns a source manifest whose
deduplicated watch projection includes the selected input, resolved and unresolved source graph,
project manifest, declared entry, and file-backed assets. Missing dependency, entry, and asset paths
remain in the projection on failure so their creation can recover the project. The host owns the
polling or OS-watcher mechanism and recompiles the same input after a relevant change.
The web methods return JavaScript objects and typed arrays; C uses opaque handles and two-pass
buffer sizing. Those are transport differences only. Image serialization, content and asset
digests, path validation, source replay, resource limits, and materialized files are canonical Rust
operations shared by both. loadProjectFiles(files, projectFilePath) and
onda_project_image_load_files(..., project_file_path_utf8, ...) accept an explicit manifest
path when the file set contains multiple projects; omitting it requires an unambiguous manifest.
Portable project exports require a successful compilation. Loading extracted project files rejects
a reachable source graph that cannot be loaded and parsed, while
compilation also verifies that each bound asset names a declared buffer and matches its primitive
element type and fixed channel count.