MIDI to realistic instrument audio, for AI agents
Published October 2026
An agent can plan a part and write the notes. What it cannot do on its own is make those notes sound like a player. doseedo_render_midi is that step as a hosted tool: send the notes (in beats, at your tempo), name an instrument from the open catalog, and get back audio takes played by an instrument model trained on isolated instrument tracks from real recording sessions. There are 40+ instruments, from orchestral strings, brass and winds to guitars, basses, keys, drums and World instruments. Each part has one player and comes back as its own stem, measured against the notes you sent. The same render is one MCP call or one POST /api/jobs, and a whole arrangement goes in one call with doseedo_render_score.
The problem: General MIDI sounds like General MIDI
The usual way to hear an agent's MIDI is to play it through a General MIDI SoundFont with a software synth such as FluidSynth. That proves the notes are there, but it doesn't sound like a cello section or a trumpet player, so it can't ship in a film cue, a game or a demo. Rendering on a modelled instrument is a GPU job, and most agents have no GPU, SoundFont library or synth to run it on.
Doseedo moves that step to a hosted server. The agent keeps the parts it already writes and hands them over as note rows. The server plays them on the instrument you name and returns pre-signed links to the takes. Nothing runs locally except the call.
Connect
claude mcp add --transport http --scope user doseedo https://api.doseedo.com/mcp
That line is for Claude Code. The same URL installs in the Claude apps, ChatGPT, Cursor, Gemini CLI, Codex and VS Code (one page per client in the block below). Four tools answer before you sign in: doseedo_instruments, doseedo_quote, doseedo_recipes and doseedo_edit_ops_reference. The agent can pick an instrument and price a render before anyone authenticates. Headless clients send X-API-Key: dsk_live_… from Settings → API keys.
Pick the instrument: doseedo_instruments
The catalog is the source of truth for what can play. It needs no key and has 45 entries as of October 22, 2026. Each entry carries the exact call to pass to the render, the range in concert pitch (MIDI 60 = C4), whether one player can hold chords, the presets, and a public demo render. This is the trumpet entry as the live tool returns it:
{
"id": "trumpet",
"label": "Trumpet",
"family": "brass",
"call": { "instrument": "trumpet" },
"range": "F#3–D6",
"presets": [ "open", "muted" ],
"demo": "https://doseedo.com/sources/renders/v3/melody/trumpet.m4a"
}
- Brass, winds, strings: trumpet, trombone, French horn, flute, clarinet, oboe, bassoon, sax (four registers as presets), violin, viola and cello (each with presets such as pizz), double bass, and string, brass and wind ensembles.
- Band: acoustic and electric piano, vibraphone, acoustic and electric guitar, electric bass, drum kit, percussion, electronic drums, synth.
- World: koto, sitar, shamisen, banjo, jarana, pedal steel, tin whistle, accordion, kalimba, hang, timpani, tabla, congas, bongos, bodhrán, udu and more. A World instrument is a voice model plus a timbre preset, and its
callspells out both. Koto, for example, is{"instrument":"acoustic_guitar","timbre_preset":"koto:preset1"}.
Search it the way an agent thinks: query:"warm low brass", pitches:[…] with the notes of the part (it returns instruments whose range holds them all), or id:"cello" for one entry in full, with every preset and its own range.
Render the notes: doseedo_render_midi
doseedo_render_midi {
"instrument": "cello",
"bpm": 72,
"takes": 3,
"notes": [
{ "pitch": 48, "start_beats": 0, "duration_beats": 2, "velocity": 80 },
{ "pitch": 55, "start_beats": 2, "duration_beats": 2, "velocity": 88 },
{ "pitch": 52, "start_beats": 4, "duration_beats": 1, "velocity": 84 },
{ "pitch": 53, "start_beats": 5, "duration_beats": 1, "velocity": 84 },
{ "pitch": 55, "start_beats": 6, "duration_beats": 2, "velocity": 92 }
]
}
- Notes as written. Beats are absolute from bar 1 (beat 0 = bar 1, beat 1) at
bpm. The gateway writes a Standard MIDI File at exactly that tempo and sends it as given, with no snapping or quantizing. Instead ofnotes, pass amidi_url(a.midup to 2 MiB) or asession_id+track_idto render a track already in a session. - Tempo changes.
tempo_mapis a list of[{beat, bpm}]points, each holding until the next, written into the MIDI, so a ritardando or a cue stretched to land on a film hit plays as written.doseedo_tempo_mapbuilds one from hit times (see the film-scoring guide). Passbpmas well: it is the tempo the map starts from. - One player per part. A part is one line. For chords on a single player (a piano part, string pads), set
voice_split:true: the notes are split into the voices that instrument can hold, each rendered as its own output, andresult.voicesmaps every voice to its files and notes. - Takes.
takes1–4 render in one GPU batch at no extra cost. Omit it and the server picks best-of-N for the part length.takes[0]is the scorer's pick, and the result'sseedre-renders close to the same take. - Range and typos are caught before anything is spent. An instrument name close to a catalog one (
"violn") is refused withdid_you_mean. Notes outside the range are still sent as written, but the submit reply carries arange_warningwith a fix: move the part an octave, or one of the instruments that holds every note. - The sound.
timbre_presetpicks a preset from the catalog ("violin:pizz","sax:alto").timbre_audio_urlis timbre transfer: your notes take on the sound of a short recording (its first 30 s are used).lora_idplays your own custom instrument.dynamics("p", "mf", "ff", …) steers the timbre pick. - Optional correction.
refine:"light"re-times attacks onto the score and mutes misfires."shred"plays exactly the written line and mutes anything the score never wrote. The raw render stays downloadable either way. - Redo one bar, keep the rest.
revise_take_url(a take's URL as a render returned it) withrevise_start_beatsandrevise_end_beatsregenerates only that region, steered bynotes(send the whole part, with your corrections), and splices it back with short crossfades. The rest of the take is kept as it was.
Long jobs follow one shape everywhere: the submit tool returns a job_id, doseedo_wait_task polls it server-side (one call holds at most 50 seconds, so the agent calls again while it is still running), and the result arrives as pre-signed download links that work with a plain curl for 24 hours, with no key on the download. Those links are reusable as the input of the next tool, so a separated stem feeds the transcriber and a generated take lands on a session track without a round trip through your disk.
The same render over HTTP: the jobs API
Every MCP job is also a plain REST resource, which suits a pipeline without an MCP client. Submit with POST /api/jobs. An Idempotency-Key makes a retried submit return the same job instead of a second render:
curl -s -X POST https://api.doseedo.com/api/jobs \
-H "X-API-Key: $DOO_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cue-1m1-cello-v1' \
-d '{"kind":"render_midi","input":{"instrument":"cello","bpm":72,"takes":2,
"notes":[{"pitch":48,"start_beats":0,"duration_beats":2,"velocity":80},
{"pitch":55,"start_beats":2,"duration_beats":2,"velocity":88}]}}'
The reply is a job ("status":"queued", an estimate, eta_seconds, and urls.wait). Long-poll it. One call holds at most 50 seconds:
curl -s "https://api.doseedo.com/api/jobs/job_…?wait=50" -H "X-API-Key: $DOO_API_KEY"
This is the finished job from the capture behind this page (October 22, 2026), trimmed to its shape:
{
"object": "job",
"kind": "render_midi",
"status": "succeeded",
"output": {
"takes": [
{ "url": "https://api.doseedo.com/api/files/…/stemphonic_468408271_n0.wav", "rank": 1 },
{ "url": "https://api.doseedo.com/api/files/…/stemphonic_468408271_n1.wav", "rank": 2 }
],
"seed": 468408271,
"duration": 8.67
},
"artifacts": [
{ "url": "…_n0.wav", "type": "audio", "expires_at": "2026-10-23T13:00:00.000Z" },
{ "url": "…_n1.wav", "type": "audio", "expires_at": "2026-10-23T13:00:00.000Z" }
]
}
Download each take with a plain curl -OJ <url> (no header). Instead of polling, you can stream state changes from GET /api/jobs/{id}/events (Server-Sent Events) or pass "webhook":{"url":"https://…"} on the submit. The per-take check is added by the MCP tools. Over HTTP, measure a take with {"kind":"check_take","input":{"audio_url":…,"notes":…,"bpm":72}}, which answers in the submit response. The full schema of every kind is open at GET https://api.doseedo.com/api/jobs/kinds/render_midi, and /api/jobs/openapi.json has the whole API as OpenAPI 3.1.
Every take comes back measured
A render is a performance, not a playback, and a take can miss or change a note. So the server measures each one before the agent hands it on. When the part was sent as notes, the finished result that doseedo_wait_task returns carries a check on every take, from an independent note transcriber (basic-pitch) matched against your notes: same pitch, onset within ±50 ms.
"takes": [
{ "url": "…_n0.wav", "rank": 1,
"check": {
"verdict": "good" | "check" | "bad",
"reasons": [ "…plain words, only when it is not good…" ],
"notes_heard": "<matched>/<sent>",
"recall": …, "precision": …, "octave_errors": …,
"onset_error_ms": { "median": …, "p90": …, "max": … },
"first_missed": [ { "pitch": …, "start_beats": …, "start_s": … } ],
"lufs": …, "peak_dbfs": …
} },
…
],
"take_check": "<n>/<m> take(s) measured good against your notes …"
- What the verdict means. good: at least 85% of the written notes were heard at the right pitch and time, the median onset is within 40 ms, nothing clips, the level is usable, and no written note has half a second of silence inside it. check: something to listen for, such as missed notes (between half and 85% heard), a take that runs late or early as a whole, a quiet take, or a drop-out. bad: fewer than half the notes heard (unless one constant offset recovers them, which is a timing check instead), more than 0.1% of samples clipped, or a silent take. The reasons say which, in plain words.
- Measure any take.
doseedo_check_taketakes anaudio_url(a take, a stem, any recording) and the notes it should play (notes+bpm/tempo_map, or amidi_url) and returns the full report. It is synchronous, a few seconds, CPU only, no credits. A drum or percussion part is matched by onsets instead of pitch. A render from amidi_url, a session track orvoice_splitcarries no per-take check, so check those takes this way. - Then fix, don't re-roll blind.
first_missednames the beats. Re-render that region withrevise_take_url+revise_start_beats/revise_end_beats, try the next take, or move the part into a range the instrument plays comfortably. - The transcriber is a second model, not ground truth. Very fast runs, repeated notes without a re-attack, and dense chords read less reliably, so a low score on such material means listen, not that the take is wrong. Offer the user the takes, say which one you picked and why, and never promise note-perfection.
What it costs
Ten credits per 30-second window of rendered audio, from the account's one credit pool. Takes are free, and a chord part split into voices costs one batch per few voices. doseedo_quote prices a call before it runs, spends nothing and needs no sign-in. This is its reply for the five-note cello part above, captured October 22, 2026:
{
"kind": "render_midi",
"estimate": { "generation": 10 },
"basis": "10 credits per 30 s window (8.67 s → 1 window) → 10 credits",
"assumptions": [
"takes (3) render in one GPU batch and cost nothing extra",
"a chord part split into voices (voice_split) costs one batch per ~6 voices — pass voices to price it",
"estimate only — the plane gate is authoritative and a refusal names the exact price",
"failed jobs are refunded; free accounts also have a monthly ceiling"
]
}
A render runs to the last note plus 2 seconds of release unless you set duration_seconds. Windows overlap by two seconds, so a 62-second part is three windows (30 credits). Checking a take with doseedo_check_take costs no credits. The free tier covers the first renders, and pricing has the plans.
Honest limits
- Timing is a performance too. Measured on production renders on October 22, 2026, note onsets land within about ±30 ms of the score, with a median of about 5 ms. That is about a frame at 24 fps. Tight for a cue, not sample-accurate, so report the take's
checkrather than promising a grid. - One part per call here. For a whole arrangement (up to 8 stems a call, on the same tempo map), use
doseedo_render_score, which renders every part in one batch and returns a stem per part plus a mix. - Not every entry is an instrument model.
synthplays a one-shot patch made by Stable Audio Open for the part, andelectronicdrums play a generated one-shot kit.result.oneshotslists the samples. - Instrumental only. Vocal lines are not rendered.
- Ranges are real. Write at concert pitch, inside the range. A note outside it is sent but flagged, and the model bends or fudges it.
Render one part from your agent
Connect the server, ask the agent to look up an instrument in doseedo_instruments, write eight bars and render three takes.
The Doseedo MCP server →FAQ
Is there an API that turns MIDI into realistic instrument audio?
Yes. Doseedo's doseedo_render_midi (an MCP tool, and kind "render_midi" on the jobs API at api.doseedo.com/api/jobs) plays notes you send, or a .mid file, on 40+ AI instruments. It returns audio takes as pre-signed links. It is hosted, so the caller needs no synth, SoundFont or GPU.
Does it play my exact notes?
The notes are sent exactly as written, at your bpm or tempo map, with no snapping or quantizing. The render is a performance by an instrument model, so a take can miss or change a note. Each take comes back with a check (notes heard at the right pitch and time, timing in milliseconds, loudness, a good / check / bad verdict), and doseedo_check_take measures any take on demand.
Can it follow tempo changes?
Yes. Pass tempo_map as [{beat, bpm}] points and the MIDI is written with those changes, so a ritardando or a cue stretched to land on a film hit plays as written. doseedo_tempo_map builds a tempo map from hit points or SMPTE timecodes, for free.
Can it render a whole score at once?
Use doseedo_render_score: parts you wrote, a multi-track MIDI file or a MusicXML export, every part played by its instrument in one batch, one stem per part plus a mix, up to 8 stems a call. doseedo_session_from_stems then builds a Logic Pro or Ableton Live project from the result.
Which instruments can it play?
The open catalog, doseedo_instruments, lists them with the exact call for each: orchestral strings, brass and winds, guitars, basses, keys, drums and percussion, and World instruments such as koto, sitar, banjo, shamisen and pedal steel. It had 45 entries on October 22, 2026, and needs no key.
Can it play chords?
Instruments marked plays_chords in the catalog can. Pass voice_split: true and the chords are split into the voices one player of that instrument can hold, each rendered as its own output, with result.voices mapping voices to files.
What does a render cost?
Ten credits per 30-second window of audio, with up to four takes in the same call at no extra cost. doseedo_quote prices a call first without spending anything, and a free tier covers the first renders.
Can it use my own sound?
Yes, in two ways. Pass a short recording as timbre_audio_url and the render takes on its tone, or train a private custom instrument from 30 seconds to 3 minutes of a recording with doseedo_train_instrument and pass its lora_id.
How is this different from rendering with FluidSynth and a General MIDI SoundFont?
A SoundFont plays a stored sample for each note. Doseedo renders the part with an instrument model trained on isolated instrument tracks from real recording sessions, on its GPUs, and returns several takes to choose from.
The Doseedo MCP server · one URL, every client
- Logic Proedits written into your project
- Ableton Livebuild, read, convert .als
- FL Studioread, convert .flp
- Pro Toolsmove .ptx between DAWs
- REAPERread, edit, convert .rpp
- Any AI agentwhat works today
- Claude & Claude Codephone, desktop, terminal
- ChatGPTas a custom MCP server
- Cursormcp.json, one entry
- Gemini CLIgemini mcp add
- OpenAI Codexcodex mcp add
- VS Code Copilotmcp.json, type http
- All 42 toolsthe reference
- Convert projectsten DAW formats, 75 directions
- Separate stemsvocals, drums, bass, more
- Audio to MIDItranscription, chords, key
- Generate musicsongs, parts, covers
- Render MIDIyour notes, 40+ AI instruments
- Custom instrumentstrained from a recording
- Sound effectshits, loops, textures
- Score a filmspotting to stems
- Bounce offlinethrough real plugins
- Swap a drum packaudio stays local