MIDI
audiolla reads, writes and renders Standard MIDI Files. Transcribe a recording to MIDI, compose a song from a JSON spec, build a drum pattern from step arrays, turn a detected chord progression into MIDI, edit an existing file, and render any of it back to audio through a SoundFont. No AI runs in the compose path: the spec is the music, written by hand, by a script, or by an LLM agent.
Most endpoints here follow the standard contract: a JSON body with file_path xor file_url, and output_path xor output_url for anything that produces a file (see Getting started). Three endpoints differ, because their JSON body is the musical spec itself: /v1/midi/compose, /v1/midi/generate and /v1/midi/drum take the output destination as query parameters, and without one they return the raw MIDI or audio bytes in the response. Field-level schemas are in the REST API reference.
Engines: basic-pitch for transcription, chord-detect for chords to MIDI, midi-render (FluidSynth) for rendering, and midi-compose for everything else. All of them ship in both the CPU and the CUDA image (Engines). A missing engine is a 404.
Of the endpoints on this page, only /v1/audio/to_midi/{engine} runs as a background job when you pass async_job: true (Async jobs and webhooks). The others always run inline, so give them output_path or output_url.
Audio-to-MIDI transcription
Polyphonic audio-to-MIDI via Spotify’s basic-pitch (ONNX backend, no TensorFlow). Play guitar, hum a melody, record a piano riff, and get a MIDI file back with the notes.
# Any audio -> MIDI file (staged)
curl -X POST http://localhost:8000/v1/audio/to_midi/basic-pitch \
-H 'Content-Type: application/json' \
-d '{"file_path":"uploads/guitar_riff.wav","output_path":"out/riff.mid"}'
curl -o riff.mid http://localhost:8000/v1/files/out/riff.mid
# Tune the detection thresholds
curl -X POST http://localhost:8000/v1/audio/to_midi/basic-pitch \
-H 'Content-Type: application/json' \
-d '{
"file_path": "uploads/piano.wav",
"onset_threshold": 0.6,
"frame_threshold": 0.3,
"minimum_note_length_ms": 80,
"output_path": "out/piano.mid"
}'
# Write directly to a different staging path
curl -X POST http://localhost:8000/v1/audio/to_midi/basic-pitch \
-H 'Content-Type: application/json' \
-d '{"file_path":"recordings/bass.wav","output_path":"midi/bass_notes.mid"}'
# -> {"path":"midi/bass_notes.mid","size":...,"engine":"basic-pitch","output_format":"mid"}
Optional params: onset_threshold (default 0.5), frame_threshold (default 0.3), minimum_note_length_ms (default 58), minimum_frequency / maximum_frequency (Hz, unset means unconstrained), multiple_pitch_bends (default false), melodia_trick (default true, helps with melodic content). basic-pitch is the only transcription engine; any other slug in the path is a 404 (unknown) or a 400 (not a transcription engine). The engine unloads other loaded engines before it runs.
Feed the MIDI file to /v1/midi/inspect or /v1/midi/render for a full audio to MIDI to audio round trip.
Compose MIDI
POST a JSON song spec, get a Standard MIDI File back. Write the spec by hand, generate it from a tracker or sequencer, script it in a notebook, or have an LLM produce it.
# 4-beat C major arpeggio at 120 BPM, piano + kick drum
curl -X POST 'http://localhost:8000/v1/midi/compose?output_path=midi/song.mid' \
-H 'Content-Type: application/json' \
-d '{
"tempo_bpm": 120,
"tracks": [
{"name":"Lead","program":0,"channel":0,"notes":[
{"pitch":60,"start_beats":0.0,"duration_beats":0.5,"velocity":100},
{"pitch":64,"start_beats":0.5,"duration_beats":0.5,"velocity":100},
{"pitch":67,"start_beats":1.0,"duration_beats":0.5,"velocity":100},
{"pitch":72,"start_beats":1.5,"duration_beats":0.5,"velocity":100}
]},
{"name":"Kick","program":0,"channel":9,"notes":[
{"pitch":36,"start_beats":0.0,"duration_beats":0.1,"velocity":110},
{"pitch":36,"start_beats":1.0,"duration_beats":0.1,"velocity":110},
{"pitch":36,"start_beats":2.0,"duration_beats":0.1,"velocity":110},
{"pitch":36,"start_beats":3.0,"duration_beats":0.1,"velocity":110}
]}
]
}'
# -> {"path":"midi/song.mid","size":...,"engine":"midi-compose"}
curl -o song.mid http://localhost:8000/v1/files/midi/song.mid
# Use a JSON spec file and get the MIDI bytes straight back
curl -X POST http://localhost:8000/v1/midi/compose \
-H 'Content-Type: application/json' \
-d @spec.json \
-o song.mid
The body is the spec and nothing else. Pass output_path or output_url as a query parameter to stage or upload the file and get JSON back; with neither, the response is the MIDI file itself (audio/midi, composed.mid). The endpoint also accepts multipart form data with the spec as JSON in a spec field and the destination in output_path / output_url fields.
Spec fields (the engine checks every one and answers 400 with the offending path, for example tracks[0].notes[3].pitch must be in [0, 127], got 130):
| Field | Default | Valid values |
|---|---|---|
tempo_bpm | 120 | 1 to 999 |
time_signature | [4, 4] | [numerator, denominator], numerator 1 to 32, denominator 1, 2, 4, 8, 16 or 32 |
key_signature | none | a key like "C", "Am", "F#", "Bbm" |
ticks_per_beat | 480 | 24 to 1920 |
tracks | required | non-empty list of track objects |
tracks[].name | none | any string |
tracks[].program | 0 | GM program 0 to 127 |
tracks[].channel | 0 | 0 to 15; channel 9 is the GM drum channel |
tracks[].volume | 100 | 0 to 127 (CC 7) |
tracks[].pan | 64 | 0 to 127 (CC 10) |
tracks[].notes | required | list of note objects (may be empty) |
notes[].pitch | required | 0 to 127 (60 is middle C) |
notes[].start_beats | 0.0 | 0 or more |
notes[].duration_beats | required | at least 1/64 of a beat |
notes[].velocity | 100 | 1 to 127 |
Time is in beats. On channel 9, pitches map to the GM drum kit (36 kick, 38 snare, 42 closed hi-hat, and so on).
Inspect MIDI
Read the structure of any Standard MIDI File as JSON.
curl -X POST http://localhost:8000/v1/midi/inspect \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/song.mid"}'
# -> {"type":1,"ticks_per_beat":480,"length_seconds":2.0,
# "tempo_changes":[{"tick":0,"bpm":120.0}],
# "time_signatures":[{"tick":0,"numerator":4,"denominator":4}],
# "key_signatures":[],
# "tracks":[{"index":1,"name":"Lead","length_ticks":960,"length_beats":2.0,
# "note_on_count":4,"note_off_count":4,"channels":[0],"programs":[0]},...],
# "track_count":3,"size_bytes":...}
An empty or unparseable file is a 400.
Transform MIDI
Transpose, quantize, re-tempo or filter channels in an existing MIDI file.
# transpose all non-drum tracks up an octave
curl -X POST http://localhost:8000/v1/midi/transform \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/song.mid","transpose_semitones":12,"output_path":"midi/transposed.mid"}'
# override tempo to 140 BPM and save to staging
curl -X POST http://localhost:8000/v1/midi/transform \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/song.mid","tempo_bpm":140,"output_path":"midi/fast.mid"}'
# drop the drum channel (9)
curl -X POST http://localhost:8000/v1/midi/transform \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/song.mid","drop_channels":"9","output_path":"midi/no-drums.mid"}'
# keep only channels 0 and 1
curl -X POST http://localhost:8000/v1/midi/transform \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/song.mid","keep_channels":"0,1","output_path":"midi/two-ch.mid"}'
# quantize to 1/16th notes
curl -X POST http://localhost:8000/v1/midi/transform \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/song.mid","quantize_grid_beats":0.25,"output_path":"midi/quantized.mid"}'
transpose_semitones is -48 to 48 (default 0). It never touches channel 9, and a note pushed outside 0 to 127 is dropped. quantize_grid_beats is in beats (0.25 is a 16th note in 4/4) and must be above 0; it snaps note starts and keeps durations. tempo_bpm (1 to 999) replaces every tempo event. keep_channels and drop_channels are comma-separated channel numbers in a string ("0,1"), and only one of them can be set per request. The response adds engine, transpose_semitones, quantize_grid_beats and tempo_bpm to the usual path / url and size.
MIDI quantize
Snap note starts to a rhythmic grid. This is a shorter way to call the transform above with only quantize_grid_beats.
# Quantize to 16th notes (0.25 beats)
curl -X POST http://localhost:8000/v1/midi/quantize \
-H 'Content-Type: application/json' \
-d '{"file_path":"uploads/sloppy.mid","grid_beats":0.25,"output_path":"out/tight.mid"}'
# 8th note grid
curl -X POST http://localhost:8000/v1/midi/quantize \
-H 'Content-Type: application/json' \
-d '{"file_path":"recorded.mid","grid_beats":0.5,"output_path":"midi/quantized.mid"}'
grid_beats is the grid size in beats: 0.25 is a 16th note, 0.5 an 8th, 1.0 a quarter. Default 0.25; 0 or below is a 400.
MIDI humanize
Add small timing and velocity variations so a programmed part sounds less mechanical. The jitter is uniformly distributed and, when you pass a seed, the same input gives the same output every time.
# Gentle humanize with defaults (up to 10 ms timing, up to 10% velocity)
curl -X POST http://localhost:8000/v1/midi/humanize \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/rigid.mid","output_path":"midi/human.mid"}'
# Heavier feel with a fixed seed for reproducible results
curl -X POST http://localhost:8000/v1/midi/humanize \
-H 'Content-Type: application/json' \
-d '{
"file_path": "midi/drums.mid",
"timing_ms": 20,
"velocity_pct": 15,
"seed": 42,
"output_path": "midi/drums_human.mid"
}'
# Stage output under a different path
curl -X POST http://localhost:8000/v1/midi/humanize \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/pattern.mid","timing_ms":8,"output_path":"midi/pattern_human.mid"}'
# -> {"path":"midi/pattern_human.mid","size":...,"timing_ms":8.0,"velocity_pct":10.0}
Optional params: timing_ms (maximum shift either way, 0 to 500, default 10), velocity_pct (maximum change either way as a percentage of 127, 0 to 50, default 10), seed (any integer). Input that is not a MIDI file is a 400. This endpoint reads its input from file_path only; file_url is rejected with a 400.
Render MIDI to audio
Synthesize a MIDI file through FluidSynth and a SoundFont. Engine: midi-render.
# Synthesise via the bundled FluidR3_GM SoundFont
curl -X POST http://localhost:8000/v1/midi/render \
-H 'Content-Type: application/json' \
-d '{"file_path":"midi/song.mid","output_format":"wav","output_path":"out/song.wav"}'
curl -o song.wav http://localhost:8000/v1/files/out/song.wav
# Use your own SoundFont (must be staged first)
curl -X PUT --data-binary @my.sf2 \
-H 'Content-Type: application/octet-stream' \
http://localhost:8000/v1/files/sf/orchestral.sf2
curl -X POST http://localhost:8000/v1/midi/render \
-H 'Content-Type: application/json' \
-d '{
"file_path": "midi/song.mid",
"soundfont_path": "sf/orchestral.sf2",
"output_format": "flac",
"output_path": "out/orch.flac"
}'
Both images ship FluidR3_GM and point AUDIOLLA_SOUNDFONT at it (Configuration). soundfont_path is a path in the staging area, checked like any file_path; a missing file is a 400. Other params: gain (0.0 to 5.0, default 0.5), samplerate (22050, 44100, 48000, 88200 or 96000; default 44100) and output_format (wav, mp3, flac, opus, aac or pcm; default wav). Input without a MIDI header is a 400.
Generate music from a spec
Compose and render in one call: the same song spec as Compose MIDI goes in, audio comes out. It needs both midi-compose and midi-render.
# Stage the rendered audio
curl -X POST 'http://localhost:8000/v1/midi/generate?output_path=out/song.wav' \
-H 'Content-Type: application/json' \
-d @spec.json
# -> {"path":"out/song.wav","size":...,"engine":"midi-generate","output_format":"wav","midi_size":...}
curl -o song.wav http://localhost:8000/v1/files/out/song.wav
# FLAC through a staged SoundFont, bytes straight back
curl -X POST 'http://localhost:8000/v1/midi/generate?output_format=flac&soundfont_path=sf/orchestral.sf2' \
-H 'Content-Type: application/json' \
-d @spec.json \
-o song.flac
As with compose, the body is only the spec. output_path, output_url, output_format (default wav), soundfont_path, gain (default 0.5) and samplerate (default 44100) are query parameters, with the same ranges as render. An unsupported output_format is a 415. Without output_path or output_url the response is the audio file itself. Multipart form data with a spec field also works, with the other knobs as form fields.
Drum pattern
Step sequencer spec in, GM drum MIDI out. Each drum voice is an array of steps; the server maps the voices to channel 9 pitches and writes a MIDI file. Swing delays every second step for a shuffled feel.
# 4-on-the-floor kick, snare on 2 and 4, busy hi-hat: 2 bars at 120 BPM
curl -X POST 'http://localhost:8000/v1/midi/drum?output_path=midi/beat.mid' \
-H "Content-Type: application/json" \
-d '{
"tempo_bpm": 120,
"steps": 16,
"bars": 2,
"swing": 0.0,
"pattern": {
"kick": [1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0],
"snare": [0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0],
"hihat": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
}
}'
# -> {"path":"midi/beat.mid","size":...}
curl -o beat.mid http://localhost:8000/v1/files/midi/beat.mid
# Swing groove: 0.1 = subtle, 0.5 = strong shuffle
curl -X POST 'http://localhost:8000/v1/midi/drum?output_path=midi/groove.mid' \
-H "Content-Type: application/json" \
-d '{
"tempo_bpm": 95,
"steps": 16,
"bars": 1,
"swing": 0.2,
"pattern": {
"kick": [1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0],
"snare": [0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0],
"hihat": [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0]
}
}'
Like compose, the body is the pattern spec and the destination goes in the output_path or output_url query parameter; without one the MIDI bytes come back directly.
Body fields: tempo_bpm (1 to 999, default 120), steps (steps per 4/4 bar, 1 to 64, default 16), bars (1 to 64, default 1, the pattern repeats every bar), swing (0.0 to 0.5, default 0.0) and pattern (required, non-empty object of voice name to step array). A step of 0 is a rest and 1 is a hit at velocity 100; an integer from 2 to 127 is a hit at that velocity.
Voice names: kick, snare, hihat, hihat_closed, hihat_open, ride, crash, tom_high, tom_mid, tom_low, clap, rim, cowbell, tambourine, shaker, wood_block. A key that is a number from 0 to 127 is used as the MIDI note directly. Any other key is a 400 that lists the supported names.
Chords to MIDI
Detect the chord progression of an audio file and write each chord segment as a held MIDI chord (root, third and fifth). Use it to export a chord chart as playable MIDI or to seed a DAW session. Engine: chord-detect.
# Audio -> chord MIDI at 120 BPM
curl -X POST http://localhost:8000/v1/audio/chords-to-midi \
-H 'Content-Type: application/json' \
-d '{"file_path":"uploads/track.wav","output_path":"out/chords.mid"}'
# -> {"path":"out/chords.mid","size":...,"chord_count":42,"key":"A minor","tempo_bpm":120.0}
# Set tempo, velocity and octave
curl -X POST http://localhost:8000/v1/audio/chords-to-midi \
-H 'Content-Type: application/json' \
-d '{
"file_path": "uploads/song.wav",
"tempo_bpm": 120,
"velocity": 90,
"octave": 3,
"output_path": "out/chords.mid"
}'
# Stage the output under a different path
curl -X POST http://localhost:8000/v1/audio/chords-to-midi \
-H 'Content-Type: application/json' \
-d '{"file_path":"sessions/song.wav","output_path":"midi/song_chords.mid"}'
Optional params: tempo_bpm (1 to 999, default 120; the tempo is not detected from the audio), velocity (1 to 127, default 80), octave (1 to 7, default 4). Each segment from chord detection becomes one chord: root, major or minor third, and perfect fifth, held for the segment’s length. Audio with no detected chords is a 400.