Make your first piece of music
Start a Morning song Project with a warm folk-pop recipe. Get a Candidate you can hear, inspect the score, and decide whether to save it.
What you will get
Section titled “What you will get”An approximately 35-second FLAC and the ABC score from the same generation. The style, original lyrics, and seed below were verified in a real generation; the home-page MP3 is a compressed copy of that run. Check quality and duration again when rerunning.
You can start without a Project. Follow preparation to run a ready real Runtime and application API. Fake mode can help you practice the save flow, but its audio is a test tone.
1. Describe the sound you want
Section titled “1. Describe the sound you want”This recipe specifies language and genre first, followed by voice, two main instruments, and rhythm. Clear priorities make adjustments easier than a collection of conflicting style words.
Verified style · style
English, gentle folk pop, warm clear voice, acoustic guitar and piano, light bass and drums, 96 BPMKeep it unchanged first. Try electronic or quieter arrangements separately in the next chapter. BPM and style words express intent; exact adherence is not guaranteed.
2. Give the fragment a small theme
Section titled “2. Give the fragment a small theme”The example is about morning and companionship. Keep lines short and use Verse and Chorus sections. A 35-second fragment may not sing every line. First listen for natural language and phrasing.
Original lyrics · lyrics
[Verse]Morning gathers on the windowQuiet streets begin to glowI will carry one small promiseEvery step can help it grow
[Chorus]Walk with me into the daylightLet the little river flowWe can make a brighter momentWith the kindness that we showComplete example: services/api/examples/creator/morning-song-lyrics.txt ↗
These original lyrics and the recipe are already in the controlled examples. Keep the original lines and section markers so you can change just one line in the next chapter.
3. Generate once and keep this Job
Section titled “3. Generate once and keep this Job”Use seed 2026192201. Clip length can stay empty for auto (follows the lyrics) or take a ceiling in seconds; this tutorial keeps the verified max_seconds=35. Name the Project Morning song; keep later variations in this Project.
Open “Music generation” in the formal Web, enter the style, lyrics and seed above, then choose “Generate a music clip”. Follow the workspace generation guide to check the Job, listen, inspect the score and choose “Save as a version”. Read the phase when no percentage is available. A quiet period is not a reason to submit again. Expand the supplement below when you need command-line operations.
Supplementary entrypoint: use the existing CLI
Run from the repository root in a second terminal while the API is running. The example creates the required download directory and uses the controlled lyrics above. This command creates a Project and submits generation using the configured Runtime, and downloads audio.flac and score.abc. It saves no Version. The output directory must not already exist.
uv run --project services/api --no-sync python services/api/examples/generate_save.py generate --style "English, gentle folk pop, warm clear voice, acoustic guitar and piano, light bass and drums, 96 BPM" --lyrics-file services/api/examples/creator/morning-song-lyrics.txt --seed 2026192201 --output-dir data/morning-candidate-01The command calls the existing CLI directly with the controlled lyrics above. Consult its complete controlled source below when needed.
"""Run from the repository root with the independent API project's Python."""
import argparseimport jsonfrom pathlib import Pathimport time
import httpx
def main() -> None: parser = argparse.ArgumentParser(description="Generate an unsaved Candidate or explicitly save one as a Version.") parser.add_argument("command", choices=["generate", "from-score", "save"]) parser.add_argument("--url", default="http://127.0.0.1:8000") parser.add_argument("--project-id") parser.add_argument("--style") parser.add_argument("--lyrics-file", type=Path) parser.add_argument("--seed", type=int, default=2026192201) parser.add_argument("--output-dir", type=Path) parser.add_argument("--candidate-id") parser.add_argument("--name") parser.add_argument("--parent-version-id") parser.add_argument("--source-score-id") parser.add_argument("--abc-file", type=Path) args = parser.parse_args() if args.command in {"generate", "from-score"} and (not args.style or not args.lyrics_file or not args.output_dir): parser.error("generation requires --style, --lyrics-file, and --output-dir") if args.command == "from-score" and (not args.project_id or not args.source_score_id or not args.abc_file): parser.error("from-score requires --project-id, --source-score-id, and --abc-file") if args.command == "save" and (not args.project_id or not args.candidate_id or not args.name): parser.error("save requires --project-id, --candidate-id, and --name") if args.command in {"generate", "from-score"} and args.output_dir.exists(): parser.error("use a new output directory to preserve previous downloads") with httpx.Client(base_url=args.url, timeout=15) as client: project_id = args.project_id if args.command in {"generate", "from-score"}: if project_id is None: response = client.post("/projects", json={"name": "Morning song"}) response.raise_for_status() project_id = response.json()["id"] base = "/projects/" + project_id inputs = {"style": args.style, "lyrics": args.lyrics_file.read_text(encoding="utf-8"), "seed": args.seed} if args.command == "from-score": inputs.update(abc=args.abc_file.read_text(encoding="utf-8"), source_score_id=args.source_score_id, parent_version_id=args.parent_version_id) response = client.post(base + "/jobs/" + ("generate-from-score" if args.command == "from-score" else "generate"), json=inputs) response.raise_for_status() job_id = response.json()["id"] deadline = time.monotonic() + 180 while True: response = client.get(base + "/jobs/" + job_id) response.raise_for_status() job = response.json() if job["status"] in {"completed", "failed", "cancelled"}: break if time.monotonic() >= deadline: raise RuntimeError("Wait expired; inspect Project " + project_id + " Job " + job_id + " before retrying.") time.sleep(0.2) if job["status"] != "completed": raise RuntimeError(json.dumps(job, ensure_ascii=False)) response = client.get(base + "/candidates/" + job["result"]["candidate_id"]) response.raise_for_status() candidate = response.json() args.output_dir.mkdir(parents=True) for name, asset_id in [("audio.flac", candidate["audio_asset_id"]), ("score.abc", candidate["output_snapshot"]["score"]["abc_asset_id"])]: response = client.get(base + "/assets/" + asset_id + "/content") response.raise_for_status() (args.output_dir / name).write_bytes(response.content) value = {"project_id": project_id, "candidate": candidate, "output_dir": str(args.output_dir)} else: intent = {"candidate_id": args.candidate_id, "name": args.name, "parent_version_id": args.parent_version_id} response = client.post("/projects/" + project_id + "/versions", json=intent) response.raise_for_status() value = {"project_id": project_id, "version": response.json()} print(json.dumps(value, indent=2, ensure_ascii=False))
if __name__ == "__main__": main()4. Listen, then inspect the score
Section titled “4. Listen, then inspect the score”- Choose “Listen to this music” in the Candidate, wait for the bottom player to load it, then choose “Play”. Hear the whole piece first, then check understandable vocals, accompaniment that fits the mood, and an ending without an abrupt break. You can also play the downloaded
audio.flac. - Choose “View score”. Compare melody and rhythm with the audio and note one thing to explore next. You can also open the downloaded
score.abcin your own ABC reader. - Write “what I want to keep” and “what I will change next.” Playable, validated files do not guarantee music you will like.
This is still a Candidate. Generation supplies audio and ABC. Reference Audio transcription supplies original MIDI, and the integrated editor can edit ABC, audition, and export matching MIDI.
5. Save explicitly when you like it
Section titled “5. Save explicitly when you like it”Choose a name that captures intent, such as “Morning · warm folk.” Saving keeps the inputs, outputs, and provenance. Later experiments will not overwrite this audio or score.
After saving a second Version in the same Project, open “Versions”, choose the two pieces in “Version A” and “Version B”, then choose “Use this pair”. Use the bottom player to compare at the same number of seconds and note which better fits your goal. An unsaved Candidate can play on its own; explicitly save it before it appears in the comparison choices. With one Version, you can still listen to it alone. See A/B comparison ideas for the steps and recovery actions.
If you are not satisfied, keep the Candidate and try again. You do not need to save every generation as a Version. Saving the same Candidate, name, and parent again returns the same Version.
Current save entrypoint: Candidate → Version
Replace placeholders with the actual ids printed in the previous step. This command writes a Version in the same Project.
uv run --project services/api --no-sync python services/api/examples/generate_save.py save --project-id <PROJECT_ID> --candidate-id <CANDIDATE_ID> --name "Morning - warm folk"If waiting expires, find the same result first
Section titled “If waiting expires, find the same result first”The CLI timeout message includes Project and Job ids. Query those ids with “Read Job” in local Swagger first. Check for a completed result, queue state, or error. Do not rerun generation immediately: the original Job may still be running.
If the Job is explicitly failed, read error and recovery_required. Resolve the cause first; ask the Runtime operator to check any uncertain execution. Explicitly submit a new Job only when retry is safe. Keep the old Job and Assets.
Next: the same lyrics, a different mood
Section titled “Next: the same lyrics, a different mood”Keep the Project, original lyrics, seed, and the Version you like. Change one input in the next chapter to hear the difference more clearly. For a deliberate melody change, follow Score editing and regeneration: change one phrase, audition and select it, then keep a new piece with its source parent Version.
1ce3268Page source: docs/learn/first-music.en.md