Skip to content
THIRI logo
build.thiri.ai
Music theory for agents

Manual · Part II · In production

Composer C: an agent that writes Csound with THIRI holding the harmony

A chat-driven studio where a language model edits a typed song spec, THIRI supplies every voicing, Csound renders deterministically, and nothing is called finished until it has been measured.

Updated Oct 2, 2026

Composer C is a music studio you talk to. You describe a song, a Claude agent edits a small JSON document, JavaScript Object Notation, that describes it, the document compiles to Csound, the server renders it, and the browser plays it. No digital audio workstation or plugin chain sits underneath. The code lives in the repository vibe-studio; the name Composer C appears nowhere in it. The hosted instance is composer.thiri.ai, with accounts, a guest demo, chat and THIRI harmony all on.

From a sentence to sound

Every stage is typed.

StageWhat happens
IntentA chat turn, a one-click verb or a knob change produces edit operations
EditThe operations are validated and applied to the song spec; they are the only write path
HarmonyAny chord without a voicing is fetched from THIRI and frozen into the spec
CompileThe spec becomes Csound text: one wet stem per section and stem group, where a stem is a named set of layers such as drums or bass, a mix per section, one master
RenderA queue runs the csound command line, converts to FLAC, the Free Lossless Audio Codec, and stores the result by content hash
PlayThe browser decodes the stems and sums them in Web Audio

Compile is pure and deterministic. The SHA-256 of the Csound text is the cache key, so the same bars never render twice. Stems are wet and linear; the only saturation lives in the mix and master units. Section boundaries fall on control-rate block edges, so sections overlap-add sample-exactly. The renderer is Csound 6.18.

What you touch

Six presets ship in the repository. Verb chips, named darker, punchier, breathe, solo and extend in the README, apply a fixed edit without a model call; the tiers document keeps them keyless and free at every tier. Chat edits need an Anthropic key on the server. An arrangement view shows sections and layers. MIDI out, the Musical Instrument Digital Interface, reaches your hardware through the browser’s Web MIDI, so a hosted studio can still play the synth on your desk. A .mid export exists; on the hosted instance it is gated to accounts.

With no keys at all, the studio still makes music.

The harmony fence

The agent is not allowed to invent chords.

The rule: the agent never writes pitches except inside clip rhythms; harmony comes from THIRI voicings through a pitch_source field. The agent names a chord. The harmony bridge walks the song’s timeline, finds every symbol without a voicing, and fetches each one through the THIRI MCP, the Model Context Protocol server, passing the previous chord’s notes so the engine can voice-lead. The result is snapshotted into the spec. Compile never touches the network.

The hosted instance holds its THIRI token on the server, so a visitor needs no key. If the MCP call fails, the bridge falls back to a local textbook stub and tags the voicing source="local-stub", so the app degrades honestly, not silently. Its docstring calls it plain music theory, not the THIRI engine.

One disagreement sits in that fallback. Both THIRI engines spell a dominant thirteenth with the natural eleventh present, as 0 4 7 10 14 17 21. The stub’s table, read from vibe/harmony.py on 2026-10-02, omits it: 0 4 7 10 14 21. They have not been reconciled, so a stubbed 13th chord may differ from the engine’s. The source tag on the voicing tells you which you got.

Designed instruments

Beside the hand-written orchestra voices there is a second kind: an InstrumentSpec, a JSON document in one of three families: analog, granular and FM, frequency modulation. The agent fills a schema and never writes orchestra text. The schema validates, compiles to deterministic Csound, and gets knobs automatically. The first phase was committed on 2026-09-13. Knobs become p-fields appended after the existing ones, with defaults equal to the literal they replace, so every render that existed before the knob stays byte-identical.

The skill’s rules came from measurements during that build:

  • FM here is phase modulation. Feedback runs inside a user-defined opcode at a control rate of one sample. Setting that rate at instrument level corrupted the global bus writes: clipping, and a left-right correlation of zero.
  • A granular cloud from a single-cycle wavetable needs random grain phase, or the grain rate combs the spectrum. Pitched chords need grains of 200 ms or more; 250 ms grains at 40 per second is exact. Grains under 80 ms smear pitch into sidebands.
  • Pulse waves away from 50 percent duty carry DC, a direct-current offset. The compiler inserts dcblock2 before the drive.
  • A five-note chord at half amplitude per note clips a hot carrier. Auditions scale by one over the square root of the polyphony.

Every instrument that came out right is saved as a recipe with its measurements, and the saver refuses anything that clips or carries DC.

Measure before claiming

The project rule is one sentence: for anything rendered, measure it with csound-listen before claiming it works. On 2026-09-13 the measurement scripts caught three defects in demos that had passed their tests: an FM patch clipping with decorrelated channels, a granular pad smearing its chord into the wrong pitches, and an analog patch carrying a 4.7 percent DC offset.

The skill turns words into numbers. From its table:

Someone saysMeasureAudible at about
brighter, darkerspectral centroid±8 percent
punchierattack under 10 ms, crest factor+2 dB crest
wider, narrowerside-to-mid level±2 dB
in tune, on the chordpitch classes and centswithin ±15 cents
brokenclipped samples, DC above 0.005, correlation near zero on a mono sourceany

A master cannot show you a buried part; render the stems and measure each one, as Scored to picture learned.

The film studio built on it

On 2026-10-01 and 02 a video-first page was built on the same spine: picture on top, one track per sound source. Seven sources were wired: Csound, THIRI strings, Stable Audio 3 running on the CPU through sa3.cpp, a Lyria RealTime bed, a Magenta ImprovRNN line, a Gemini melody, and a free library that carries each sound’s licence. A conductor track holds a tempo map at 480 pulses per quarter note with bar one on a frame; THIRI fits the tempo to the named story hits.

Measured, from the build log:

  • A Csound impact placed on frame 48 lands at sample 96,000, which at 48 kHz and 24 frames per second is frame 48 exactly.
  • The exported stems null against the mix to −126 dBFS, decibels relative to full scale.
  • A Stable Audio take equals the command-line render of the same seed to 1.2e-7, at about 33 seconds of compute per second of sound effect on this machine.
  • Lyria onsets land a median 2.3 ms from the sixteenth-note grid.
  • Magenta with seed 5 reproduces exactly, with 100 percent chord tones on strong beats and 83 percent overall.
  • Gemini puts 100 percent chord tones on strong beats, at about 35 seconds and 5,000 tokens per line.

Export writes stems that sum to the mix at −16 LUFS, Loudness Units relative to Full Scale, an MP4 with the picture stream copied untouched, a cue sheet, a credits file with each take’s licence, third-party notices where an MIT-licensed sound is used, score and conductor MIDI files, a review page and a verification report.

As of 2026-10-02 this page runs locally behind a feature flag and is not part of the hosted studio. Two things are open: the review page’s solo button plays about 20 ms behind the picture, and the master prompt can add but cannot yet remove, mute or regenerate a track in place.

Known limits

  • Note placement is hard-coded to 4/4; a spec in another meter does not yet place notes correctly.
  • The README describes a public git clone. The repository was private on 2026-10-02, so those instructions do not yet work; treat the README as the intended release shape.

Next

Porting the engine: the cross-repo rules →

Where the canonical engine lives, how the C++ port is pinned to it by golden vectors, and the eleven traps that have already cost a day each. For people who ship THIRI inside something.