Command line

The choruspop tool makes songs and versions, exports them and follows jobs, from any shell. It ships inside the direct download of ChorusPop.

Install

In ChorusPop, choose ChorusPop ▸ Install Command-Line Tool. It links the tool into /usr/local/bin if you can write there, otherwise ~/.local/bin. If that one isn't on your PATH yet:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && exec zsh
choruspop version

The tool itself lives at /Applications/ChorusPop.app/Contents/Helpers/choruspop and updates with the app. You don't need ChorusPop open: the tool opens it in the background, without taking focus, and waits for it.

Examples

# Make a song: waits, shows progress, writes the file here
choruspop make "[verse]
Neon on the river
Lights across the bay
[chorus]
Turn it up" --genre synthwave --mood dreamy --voice female --length 0:30

# Lyrics from a file (or - for stdin), a style line, into an album, as FLAC
choruspop make --lyrics-file song.txt --style "indie chill acoustic guitar male vocal" \
  --title "Harbor Lights" --album "Summer" --length 2:30 --format flac --out ~/Desktop/

# The same song again: same seed, same inputs
choruspop make --lyrics-file song.txt --genre folk --seed 1234

# In an exact key (a key in --style isn't followed)
choruspop make --lyrics-file song.txt --genre pop --key "D minor"

# From a plan: an ABC plan, or a MIDI file with the tracks you pick
choruspop make --lyrics-file song.txt --plan song.abc
choruspop tracks song.mid
choruspop make --lyrics-file song.txt --midi song.mid --roles voice=1,lead=5,harmony=2+3

# No singing (experimental: some seeds still sing)
choruspop make --instrumental --genre lofi --mood chill --length 1:00

# Versions of the newest song
choruspop strip latest --remove-vocals
choruspop strip latest --stems --format flac --out ~/Desktop/

# Export what's already made
choruspop export "Harbor Lights" --version instrumental --format alac --out ~/Desktop/
choruspop export latest --music --lrc          # to ~/Music/ChorusPop, with synced lyrics

# Find songs
choruspop list neon --limit 5
choruspop show latest                          # with the key, tempo and metre of its plan

# Queue now, follow later
job=$(choruspop make --lyrics-file song.txt --no-wait)
choruspop status "$job" --wait --timeout 900
choruspop status                               # everything being made
choruspop cancel "$job"

A SONG is its id, the first 6 or more characters of the id, its exact title, or latest. Job ids for songs become the song's id once it's done; version jobs are <song id>#instrumental, #vocals or #stems.

Keys

The model doesn't follow a key named in the style. With --key it writes its plan (tempo, key, chords, melody) first, the plan is moved to that key the nearest way (up to 6 semitones, every part by the same interval), and the song is sung from it, so the key is exact. A key can be written "D minor", Dm, "F# major", "B flat minor" or Ebm. Moving can't change a mode: a plan in a major key asked for a minor one (or the reverse) goes to the key with the same signature, so asking for D minor can give F major. choruspop show SONG says which key it is in.

From a plan

--plan FILE sings an ABC plan instead of one the model writes; --midi FILE turns a MIDI file into a plan as ChorusPop's Import MIDI does. Lyrics are optional with either: given, they're sung to the plan's Vocal line, one syllable per note. The song is made at least as long as the plan.

  • A plan is ABC in the model's dialect: the header X, T, M, L, Q, the voices V: Vocal then V: Ins, then K; under each % section, one phrase per voice with full bars. A song's own plan (the plan_path of show --json) is a good place to start. Write the Vocal line an octave above the pitch you want: the model sings it an octave lower.
  • choruspop tracks FILE.mid lists a MIDI file's tracks and the role each gets. Change them with --roles voice=N,lead=N,harmony=N+N: the voice is sung, the lead is the instrument line, the harmony tracks give the chords.
  • A plan that isn't valid is refused with the line that's wrong (exit 1, invalid).

Instrumentals

--instrumental makes a song with no singing: the model's plan (or yours) with its Vocal line silenced and the tune moved to the band. Leave the lyrics out. It's experimental: on some seeds the song still sings, which is why ChorusPop itself doesn't offer it yet.

Waiting and progress

make and strip wait for the job by default. Progress goes to stderr (one updating line in a terminal, a line per 10% otherwise), and the paths of the files written go to stdout, so file=$(choruspop make …) captures the path.

  • --no-wait prints the job id and returns at once.
  • --timeout SEC stops waiting after SEC seconds and exits 4; the job keeps going.
  • ^C stops waiting too. ChorusPop keeps making the song; choruspop status shows it, choruspop cancel JOB stops it.

When a command stops waiting (--timeout, ^C, or --no-wait with --out or --music), the files still go where it asked once the job is done: to the Music folder, or to --out (the current folder by default), moved there by the tool in the background or by the next choruspop command you run.

Songs are made one at a time. Starting several queues them; they don't run side by side. Versions (strip) wait for every song queued or being made: songs go first.

--json output

Add --json (or -j) to any command for newline-delimited JSON on stdout: zero or more events, then exactly one result or error.

{"type":"progress","job":{"id":"8f3c…","kind":"song","status":"running","progress":0.42,"stage":"performing","title":"Neon River","eta_seconds":48,"length_sec":30}}
{"type":"file","path":"/Users/me/Desktop/Neon River.flac","role":"song"}
{"type":"result","data":{"job":{"id":"8f3c…","kind":"song","status":"done","progress":1,"title":"Neon River","song_id":"8f3c…","length_sec":30},"files":["/Users/me/Desktop/Neon River.flac"]}}
{"type":"error","code":"queue_full","message":"The queue is full (10 songs)."}
Commandresult.data
make, strip{"job": Job, "files": [path…]} (files empty with --no-wait or when it didn't finish)
export{"files": [path…], "format": "flac", "version": "original"}
list{"songs": [Song…]}
show{"song": Song, "lyrics": "…", "files": {"song": path, "instrumental": path, …}, "key": "Dm", "tempo": 96, "meter": "4/4", "plan_path": path} (the last four from the song's plan; null without one)
tracks{"tracks": [{"index", "name", "instrument", "range", "notes", "line", "drums", "role"}…]}
status{"job": Job}, or {"jobs": [Job…]} without a job id
cancel{"job": Job}
version{"cli": "1.1", "app": App}

Job

KeyTypeWhat it is
idstringThe job id. A song job's id becomes the song's id; a version job is <song id>#<kind>.
kindstringOne of song, instrumental, vocals, stems.
statusstringOne of queued, running, paused, done, failed, cancelled.
progressnumber0…1.
stagestring (optional)A song's stage, in order: preparing, writing (the plan: tempo, key, chords, melody), composing, performing, mixing, saving. A version's: separating. Either, while queued or held: waiting.
titlestringThe song's title.
song_idstring (optional)The finished song (a song job, once done), or the song a version is of.
eta_secondsinteger (optional)Seconds until it's done, when the app can tell.
length_secinteger (optional)The length asked for, in seconds.
messagestring (optional)Why it failed or was cancelled.

Song

KeyTypeWhat it is
idstringThe song's id.
titlestringThe song's title.
stylestring"Synthwave · Dreamy"
genrestringGenre id.
moodsarray of stringMood ids.
voicestringfemale or male.
instrumentsarray of stringInstrument ids.
style_fedstringThe style line the model was given.
length_secintegerThe length asked for, in seconds.
audio_secnumberThe audio's actual length, in seconds.
seedintegerMake it again with this seed and the same inputs.
created_atstringWhen it was made (ISO 8601).
albumsarray of stringTitles of the albums it's in.
versionsarray of stringThe versions made so far: instrumental, vocals, stems.

App

KeyTypeWhat it is
namestringChorusPop.
versionstringThe app's version.
buildstringThe app's build number.
bundle_idstringThe app's bundle id.
protocolintegerThe version of the tool-to-app protocol.
model_readybooleanWhether the song model is installed. Songs queued before it is wait.
songsintegerHow many songs the library has.
active_jobsintegerJobs queued or running.
featuresarray of string (optional)What the app can do beyond the first commands: plan, midi, key, instrumental, deliver, plan_info, midi_tracks. Missing from apps before build 16.
# The newest song's id and seed
choruspop list --limit 1 --json | jq -r '.data.songs[0] | "\(.id) \(.seed)"'

# Make a song and keep only its path
path=$(choruspop make --lyrics-file song.txt --json | jq -r 'select(.type=="result") | .data.files[0]')

Exit status

StatusMeaning
0Done. With --no-wait: the job was queued.
1An error: the app said no (see the error code), or it couldn't be reached.
2A usage mistake: an unknown option or a value out of range.
3The job failed or was cancelled.
4Still going at --timeout. The job keeps going; follow it with choruspop status JOB.
130You pressed ^C. The job keeps going in ChorusPop.

Error codes

With --json an error is {"type":"error","code":…,"message":…}; without it, the message goes to stderr.

CodeMeaning
invalidAn argument is missing or out of range; the message says which.
not_foundNo such song, album, job or version.
queue_fullThe queue is full (10 songs). Wait for one to finish.
not_supportedThis Mac can't make songs.
busyChorusPop is busy with something that can't be interrupted. Try again shortly.
failedThe job failed.
unavailableChorusPop isn't installed, didn't start, or is older than build 6.
protocolThe tool and the app disagree (update both: they ship together).
internalA bug. Please send the message to support@choruspop.com.

A batch from a folder of lyrics

# One song per .txt file, queued without waiting, then collected in order.
# The app makes them one at a time.
for f in lyrics/*.txt; do
  choruspop make --lyrics-file "$f" --title "$(basename "$f" .txt)" \
    --genre pop --length 1:00 --album "Demo tape" --no-wait
done > jobs.txt

mkdir -p out
while read -r job; do
  choruspop status "$job" --wait --timeout 1800 && choruspop export "$job" --format flac --out out/
done < jobs.txt

The queue holds 10 songs; a longer batch gets queue_full for the rest. Queue the next one when one finishes, as the Python example does.

Commands

choruspop — make songs with ChorusPop on this Mac.

Usage:
  choruspop make LYRICS [options]        make a song (waits, then writes the file here)
  choruspop make --plan song.abc | --midi song.mid [LYRICS] [options]
  choruspop tracks song.mid              a MIDI file's tracks and the roles make --midi gives them
  choruspop strip SONG --remove-vocals | --vocals-only | --stems
  choruspop export SONG [--format flac] [--version instrumental] [--out PATH | --music]
  choruspop list [SEARCH] [--album NAME] [--limit N]
  choruspop show SONG
  choruspop status [JOB] [--wait] [--timeout SEC]
  choruspop cancel JOB
  choruspop version
  choruspop mcp                           run as an MCP server over stdio

SONG is an id, the start of one, a title, or "latest".
Add --json to any command for newline-delimited JSON on stdout.
Run `choruspop help COMMAND` for its options.

make

choruspop make LYRICS [options]
choruspop make --lyrics-file song.txt [options]      (- reads stdin)
choruspop make --plan song.abc [LYRICS] [options]
choruspop make --midi song.mid [--roles voice=1,lead=5,harmony=2+3] [LYRICS] [options]
choruspop make --instrumental [options]

Style (pick chips, or give a whole style line with --style)

OptionWhat it does
--genre Gpop, rock, hipHop, rnb, electronic, dance, indie, folk, country, jazz, blues, soul, metal, punk, reggae, lofi, synthwave
--mood Mup to 2: upbeat, happy, chill, dreamy, romantic, sad, dark, energetic, angry, epic
--voice Vfemale, male
--instrument Iup to 3: piano, acousticGuitar, electricGuitar, synth, strings, bass, drums, 808s, brass
--style TEXTthe style line as the model reads it, e.g. "synthwave dreamy female vocal"

Song

OptionWhat it does
--title TEXTdefault: the first lyric line
--length L0:30 to 4:50 in 10 s steps (default 1:00); 30 and 0:30 both work
--seed Nsame seed and inputs, same song
--album NAMEadd it to this album (made if there's none)
--key Kthe key, e.g. "D minor", "F# major", Bb, Ebm: the model writes its plan, then the plan is moved to K (by the nearest way, up to 6 semitones) and sung from there. A major plan asked for a minor key (or the reverse) goes to the key with K's signature (D minor → F major).

From a plan (lyrics are optional; they're sung to the plan's Vocal line)

OptionWhat it does
--plan FILEABC in the model's dialect (see choruspop.com/docs/cli), as Edit plan takes it
--midi FILEa MIDI file, turned into a plan as Import MIDI does it
--roles SPECwhich MIDI tracks play what: voice=N (sung), lead=N (V:Ins), harmony=N+N (the chords); `choruspop tracks FILE` lists them. Default: the same picks as Import MIDI's track list

No singing

OptionWhat it does
--instrumentalthe model's plan with its Vocal line silenced (or --plan/--midi's). Not in the app yet: some seeds still sing.

When it's done

OptionWhat it does
--format Fm4a (default), alac or flac
--out PATHa file or folder (default: this folder)
--musicsave to ~/Music/ChorusPop instead
--lrcwrite the synced lyrics (.lrc) too
--no-waitprint the job id and return at once (with --out or --music, the files still go there when it's done)
--timeout SECstop waiting after SEC; the song keeps going and its files still go to --out (or here, or --music) when it's done. So does ^C

Songs take minutes: about 1–3 min for 0:30 on an M-series Mac, longer for long songs.

Lyrics use section tags: [verse], [chorus], [bridge], [outro].

tracks

choruspop tracks FILE.mid

Each track: its number, the role make --midi gives it (voice, lead, harmony, ignore),

name, instrument, range, notes, and whether it's one line or chords.

Change roles with make --midi FILE --roles voice=N,lead=N,harmony=N+N.

strip

choruspop strip SONG --remove-vocals | --vocals-only | --stems [options]
OptionWhat it does
--remove-vocalsthe instrumental
--vocals-onlyjust the singing
--stemsdrums, bass, other and vocals, four files in a folder
--format, --out, --music, --no-wait, --timeoutas for make

Songs go first: a strip waits for every song queued or being made, then takes seconds

to a minute. If you stop waiting (--timeout, ^C, --no-wait with --out), the files still

go to --out (or here, or --music) when it's done.

export

choruspop export SONG [options]
OptionWhat it does
--version Voriginal (default), instrumental, vocals, stems
--format Fm4a (default), alac, flac
--out PATHa file or folder (default: this folder)
--musicsave to ~/Music/ChorusPop instead
--lrcthe synced lyrics (.lrc) beside it

Files carry the title, album, cover, lyrics and the model's credit and license.

list

choruspop list [SEARCH] [--album NAME] [--limit N]

Newest first. SEARCH matches titles, lyrics and style.

show

choruspop show SONG

The song's details, lyrics, and files saved to the Music folder; with its plan, the key, tempo and metre the model wrote, and the plan's path.

status

choruspop status                 everything being made
choruspop status JOB [--wait] [--timeout SEC]

Exit status with --wait: 0 done, 3 failed or cancelled, 4 still going at --timeout.

cancel

choruspop cancel JOB

Stops it if it's running, or takes it out of the queue.

version

choruspop version

The tool's and the app's versions.

mcp

choruspop mcp

Speaks the Model Context Protocol on stdin/stdout, for Claude and other MCP clients

OptionWhat it does

claude mcp add choruspop -- choruspop mcp

Environment

CHORUSPOP_APPThe ChorusPop.app to use, when the tool isn't the one inside it (for example a copy outside /Applications).
CHORUSPOP_ENDPOINTThe app's connection file, normally ~/Library/Application Support/ChorusPop/cli.json (readable only by you).