Clay Sound Plugin
Hybrid synth + sampler + tracker audio plugin for Clayground. One coherent
engine, two wrappers for pre-existing assets (Sound, Music), and a
small instrument/sequencer layer on top (SynthInstrument,
SampleInstrument, SongPlayer, ChipMood/MoodPlayer). Offline render
is first-class and deterministic.
See docs/docs/manual/dojo.md for how to hot-reload song files without
resetting the playhead (.dojoignore).
Getting Started
import Clayground.Sound
Components
Sound
For short sound effects (fire-and-forget). Supports overlapping playback with
instance pooling for efficiency.
Sound {
id: jumpSound
source: "sounds/jump.wav"
volume: 0.8
lazyLoading: false // preload by default
}
// Usage
onJumped: jumpSound.play()
onReset: jumpSound.stop() // stops all playing instances
Music
For background music with full playback controls.
Music {
id: bgMusic
source: "music/theme.mp3"
volume: 0.5
loop: true
}
// Usage
onGameStarted: bgMusic.play()
onGamePaused: bgMusic.pause()
onGameOver: bgMusic.stop()
Properties
Sound
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| source | url | "" | Audio file URL (local or remote) |
| volume | real | 1.0 | Volume level (0.0-1.0) |
| lazyLoading | bool | false | If true, load on first play() |
| loaded | bool | readonly | Whether audio data is loaded |
| status | enum | readonly | Null/Loading/Ready/Error |
Music
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| source | url | "" | Audio file URL |
| volume | real | 1.0 | Volume level (0.0-1.0) |
| lazyLoading | bool | false | If true, load on first play() |
| loaded | bool | readonly | Whether audio data is loaded |
| playing | bool | readonly | Currently playing |
| paused | bool | readonly | Currently paused |
| loop | bool | false | Loop playback |
| position | int | readonly | Current position (ms) |
| duration | int | readonly | Total duration (ms) |
SynthInstrument
Real-time oscillator voice with ADSR, pitch envelope, LFO. Trigger notes
by MIDI number or Hz. Also bakes to WAV.
SynthInstrument {
id: lead
waveform: "square" // sine | square | triangle | sawtooth | noise
attack: 0.005; decay: 0.08; sustain: 0.5; release: 0.15
volume: 0.8
}
// Fire a note
lead.triggerNote(69, 0.9, 0.25)
// Synth-to-sample bounce
var wavPath = lead.bake(69, 0.4)
SampleInstrument
PCM sample playback with loop points, root note, ADSR on top of samples.
SampleInstrument {
id: drum
source: "kick.wav"
rootNote: 60
volume: 0.9
}
drum.triggerOneShot(0.8)
SongPlayer
Plays a .song.json against QML instruments resolved by objectName.
Supports play/pause/stop/seek/loop and hot-reload without playhead reset.
SynthInstrument { id: lead; objectName: "demoLead" }
SynthInstrument { id: bass; objectName: "demoBass" }
SongPlayer {
source: "songs/demo.song.json"
instruments: [lead, bass]
loop: true
onHotReloaded: console.log("song file changed, kept playing")
}
Song file format (v1):
json
{
"tempo": 110,
"tracks": {
"lead": { "instrument": "demoLead" },
"bass": { "instrument": "demoBass" }
},
"patterns": {
"A": {
"lead": [
{ "t": 0, "note": "C5", "dur": 0.5 },
{ "t": 0.5, "note": "E5", "dur": 0.5 }
],
"bass": [{ "t": 0, "note": "C3", "dur": 2.0 }]
}
},
"sections": [ { "pattern": "A", "repeat": 4 } ]
}
Notes can be MIDI numbers or scientific pitch strings (C4, F#3, Bb5).
Defaults: dur=0.5 beats, vel=0.8.
Platform Support
WASM: Sound / Music today; full hybrid engine coming in the next
stage (AudioWorklet backend).
Desktop/Mobile: Full support — all types above work end-to-end.
Technical Notes
Audio is fully preloaded before playback (no streaming)
WASM: Web Audio API requires user gesture to start AudioContext
WASM: Remote URLs must be CORS-enabled
Desktop: Uses QAudioSink for all in-engine types, QMediaPlayer for Music
Hot-reload: SongPlayer watches its source file; drop a .dojoignore
(songs/ or *.song.json) next to your Sandbox.qml to prevent the
dojo from reloading the whole scene on song edits