
Ace Music
- 2 installs
- Updated June 27, 2026
- mz038197/vanscoding-skills
Generates AI music via ACE Music's free ACE-Step 1.5 API from a style prompt, with optional lyrics, instrumental, cover, and repaint modes.
About
Calls ACE Music's free hosted API to generate songs, beats, or instrumentals from a style prompt using a bundled generate.py script. A developer uses it to create AI music tracks, optionally with lyrics or as instrumentals.
- Free ACE-Step 1.5 API, prompt plus optional lyrics
- generate.py one-shot script writes an mp3 output path
Ace Music by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,166 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mz038197/vanscoding-skills --skill ace-musicAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| Last updated | June 27, 2026 |
| Repository | mz038197/vanscoding-skills ↗ |
What it does
Generates AI music via ACE Music's free ACE-Step 1.5 API from a style prompt, with optional lyrics, instrumental, cover, and repaint modes.
Files
ACE Music - AI Music Generation
Generate music via ACE Music's free hosted API (ACE-Step 1.5 model).
Setup
API Key is stored in env ACE_MUSIC_API_KEY. If not set: 1. Open https://acemusic.ai/playground/api-key in the browser for the user 2. Ask them to sign up (free) and paste the API key 3. Store it: add ACE_MUSIC_API_KEY=<key> to your .env or TOOLS.md, or set via PowerShell: $env:ACE_MUSIC_API_KEY="<key>"
Quick Generation
Use scripts/generate.py for one-shot generation. It reads ACE_MUSIC_API_KEY from the environment and prints the saved file path(s) to stdout.
# Simple prompt (AI decides everything)
python scripts/generate.py "upbeat pop song about summer" --duration 30 --output summer.mp3
# With lyrics (Tagged mode — recommended)
python scripts/generate.py "gentle acoustic ballad, female vocal" \
--lyrics "[Verse 1]\nSunlight through the window\n\n[Chorus]\nWe are the dreamers" \
--duration 60 --output ballad.mp3
# Instrumental only
python scripts/generate.py "lo-fi hip hop beats, chill, rainy day" --instrumental --duration 120 --output lofi.mp3
# Natural language — AI writes everything
python scripts/generate.py --sample-mode --output jazz.mp3
# Specific settings (BPM, key, seed)
python scripts/generate.py "rock anthem" --bpm 140 --key "E minor" --language en --seed 42 --output rock.mp3
# Multiple variations
python scripts/generate.py "electronic dance track" --batch 3 --output edm.mp3The script prints the saved file path(s) to stdout (one per line). Send those files to the user.
Advanced Usage (covers / repainting)
For covers and repainting, pass --task cover or --task repaint:
# Cover (requires base64 audio — see api-docs.md for multimodal input)
python scripts/generate.py "Cover this song in jazz style" --task cover --cover-strength 0.8 --output cover.mp3
# Repaint a section (seconds 10–30)
python scripts/generate.py "Add electric guitar solo" --task repaint --repaint-start 10 --repaint-end 30 --output repaint.mp3For full API spec (audio input, streaming) — see references/api-docs.md.
Parameters Guide
| Want | CLI argument |
|---|---|
| Specific style | "jazz, saxophone solo, smoky bar" as the prompt |
| Custom lyrics | --lyrics "[Verse]...[Chorus]..." |
| AI writes everything | --sample-mode |
| No vocals | --instrumental |
| Longer songs | --duration 120 (seconds) |
| Specific tempo | --bpm 120 |
| Specific key | --key "C major" |
| Multiple outputs | --batch 3 |
| Reproducible | --seed 42 |
| Non-English vocals | --language ja (zh, en, ja, ko, etc.) |
| Cover a song | --task cover --cover-strength 0.8 |
| Repaint a section | --task repaint --repaint-start 10 --repaint-end 30 |
Notes
- API is free forever (confirmed by ACE Music team)
- Base URL:
https://api.acemusic.ai - Audio returned as base64 MP3;
generate.pydecodes and saves it automatically - Duration: if omitted, AI decides based on content
- For best results, use Tagged mode (prompt + lyrics separated via XML tags)
{
"owner": "fspecii",
"slug": "ace-music",
"displayName": "ACE Music - Free Suno Alternative Generate unlimited AI music for free using ACE-Step 1.5. Full songs with vocals, lyrics, any genre, any language. No subscription, no credits, no limits. The open-source Suno alternative, powered by ACE Music's free API.",
"latest": {
"version": "1.0.0",
"publishedAt": 1771464200986,
"commit": "https://github.com/openclaw/skills/commit/c0a510771e56fb26aa12cecc444ad3d6f5b963a1"
},
"history": []
}
ACE-Step OpenRouter API Reference
Base URL: https://api.acemusic.ai Auth: Authorization: Bearer <api-key> Get API Key: https://acemusic.ai/playground/api-key
Generate Music
POST /v1/chat/completions
Request Parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | No | auto | Model ID |
messages | array | Yes | - | Chat message list |
stream | boolean | No | false | Enable streaming |
audio_config | object | No | null | Audio config (see below) |
temperature | float | No | 0.85 | LM sampling temperature |
top_p | float | No | 0.9 | Nucleus sampling |
seed | int/string | No | null | Random seed |
lyrics | string | No | "" | Lyrics (takes priority over messages) |
sample_mode | boolean | No | false | LLM auto-generates prompt/lyrics |
thinking | boolean | No | false | LLM thinking mode |
use_format | boolean | No | false | Enhance prompt/lyrics via LLM |
use_cot_caption | boolean | No | true | Rewrite music description via CoT |
use_cot_language | boolean | No | true | Auto-detect language via CoT |
guidance_scale | float | No | 7.0 | Classifier-free guidance |
batch_size | int | No | 1 | Number of samples |
task_type | string | No | "text2music" | Task: text2music, cover, repaint |
repainting_start | float | No | 0.0 | Repaint start (seconds) |
repainting_end | float | No | null | Repaint end (seconds) |
audio_cover_strength | float | No | 1.0 | Cover strength (0.0-1.0) |
audio_config Object
| Field | Type | Default | Description |
|---|---|---|---|
duration | float | null | Duration in seconds |
bpm | integer | null | Beats per minute |
vocal_language | string | "en" | Language code (en, zh, ja, etc.) |
instrumental | boolean | null | Instrumental only (no vocals) |
format | string | "mp3" | Output format |
key_scale | string | null | Musical key (e.g. "C major") |
time_signature | string | null | Time signature (e.g. "4/4") |
Input Modes
Mode 1: Tagged (Recommended)
{
"messages": [{"role": "user", "content": "<prompt>A gentle acoustic ballad, female vocal</prompt>\n<lyrics>[Verse 1]\nSunlight through the window\n\n[Chorus]\nWe are the dreamers</lyrics>"}],
"audio_config": {"duration": 30, "vocal_language": "en"}
}Mode 2: Natural Language (sample_mode)
{
"messages": [{"role": "user", "content": "Generate an upbeat pop song about summer"}],
"sample_mode": true,
"audio_config": {"vocal_language": "en"}
}Mode 3: Lyrics Only
{
"messages": [{"role": "user", "content": "[Verse 1]\nWalking down the street\n\n[Chorus]\nDance with me tonight"}],
"audio_config": {"duration": 30}
}Mode 4: Separate lyrics + prompt
Use lyrics field for lyrics, messages text becomes the prompt.
Audio Input (for covers/repainting)
Multimodal messages with base64 audio:
{
"messages": [{"role": "user", "content": [
{"type": "text", "text": "Cover this song in jazz style"},
{"type": "input_audio", "input_audio": {"data": "<base64>", "format": "mp3"}}
]}],
"task_type": "cover"
}Response
Audio is returned as base64 data URL in choices[0].message.audio[0].audio_url.url:
- Format:
data:audio/mpeg;base64,<base64_data> - Metadata in
choices[0].message.content(caption, BPM, duration, key, lyrics)
List Models
GET /v1/models — returns available models
Health Check
GET /health — returns {"status": "ok"}
#!/usr/bin/env python3
"""
ACE-Step Music Generation via API
Usage: python generate.py <prompt> [options]
Options:
--lyrics "..." Lyrics text
--duration 30 Duration in seconds
--language en Vocal language code (en, zh, ja, ko, ...)
--instrumental No vocals
--output file.mp3 Output file path (default: output_<timestamp>.mp3)
--bpm 120 Beats per minute
--key "C major" Musical key
--seed 42 Random seed for reproducibility
--sample-mode Let AI generate everything (prompt + lyrics)
--batch 3 Number of variations to generate
--format mp3 Output format (mp3)
--task cover Task type: text2music (default), cover, repaint
--repaint-start 10 Repaint start in seconds
--repaint-end 30 Repaint end in seconds
--cover-strength 0.8 Cover strength (0.0–1.0)
"""
import argparse
import base64
import os
import sys
import time
import requests
BASE_URL = os.environ.get("ACE_MUSIC_BASE_URL", "https://api.acemusic.ai")
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Generate music via ACE Music API", add_help=True)
p.add_argument("prompt", nargs="?", default="", help="Music style / description")
p.add_argument("--lyrics", default="")
p.add_argument("--duration", type=float, default=None)
p.add_argument("--language", default="en")
p.add_argument("--instrumental", action="store_true")
p.add_argument("--output", "-o", default=None)
p.add_argument("--bpm", type=int, default=None)
p.add_argument("--key", default=None)
p.add_argument("--seed", type=int, default=None)
p.add_argument("--sample-mode", action="store_true")
p.add_argument("--batch", type=int, default=1)
p.add_argument("--format", default="mp3")
p.add_argument("--task", default="text2music", choices=["text2music", "cover", "repaint"])
p.add_argument("--repaint-start", type=float, default=0.0)
p.add_argument("--repaint-end", type=float, default=None)
p.add_argument("--cover-strength", type=float, default=1.0)
return p.parse_args()
def build_payload(args: argparse.Namespace) -> dict:
# Build message content
if args.lyrics and args.prompt:
content = f"<prompt>{args.prompt}</prompt>\n<lyrics>{args.lyrics}</lyrics>"
elif args.lyrics:
content = args.lyrics
else:
content = args.prompt
# audio_config
audio_config: dict = {"vocal_language": args.language, "format": args.format}
if args.duration is not None:
audio_config["duration"] = args.duration
if args.bpm is not None:
audio_config["bpm"] = args.bpm
if args.key:
audio_config["key_scale"] = args.key
if args.instrumental:
audio_config["instrumental"] = True
payload: dict = {
"messages": [{"role": "user", "content": content}],
"audio_config": audio_config,
"stream": False,
"task_type": args.task,
}
if args.sample_mode:
payload["sample_mode"] = True
if args.seed is not None:
payload["seed"] = args.seed
if args.batch > 1:
payload["batch_size"] = args.batch
if args.task == "repaint":
payload["repainting_start"] = args.repaint_start
if args.repaint_end is not None:
payload["repainting_end"] = args.repaint_end
if args.task == "cover":
payload["audio_cover_strength"] = args.cover_strength
return payload
def save_outputs(audios: list, output_path: str) -> list[str]:
saved = []
base, ext = (output_path.rsplit(".", 1) if "." in output_path else (output_path, "mp3"))
for i, audio in enumerate(audios):
url = audio["audio_url"]["url"]
data = base64.b64decode(url.split(",", 1)[1])
fname = output_path if len(audios) == 1 else f"{base}_{i + 1}.{ext}"
with open(fname, "wb") as f:
f.write(data)
print(f"Saved: {fname}", file=sys.stderr)
print(fname) # stdout: file path for the caller (agent)
saved.append(fname)
return saved
def main() -> None:
args = parse_args()
api_key = os.environ.get("ACE_MUSIC_API_KEY", "")
if not api_key:
print("ERROR: ACE_MUSIC_API_KEY not set.", file=sys.stderr)
print("Get your free API key at: https://acemusic.ai/playground/api-key", file=sys.stderr)
sys.exit(1)
if not args.prompt and not args.sample_mode and not args.lyrics:
print("ERROR: Provide a prompt, --lyrics, or --sample-mode.", file=sys.stderr)
sys.exit(1)
output = args.output or f"output_{int(time.time())}.mp3"
payload = build_payload(args)
print(f"🎵 Generating music...", file=sys.stderr)
print(f" Prompt : {args.prompt or '[lyrics/sample mode]'}", file=sys.stderr)
if args.duration:
print(f" Duration : {args.duration}s", file=sys.stderr)
print(f" Language : {args.language}", file=sys.stderr)
print(f" Task : {args.task}", file=sys.stderr)
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
try:
resp = requests.post(
f"{BASE_URL}/v1/chat/completions",
headers=headers,
json=payload,
timeout=300,
)
resp.raise_for_status()
except requests.RequestException as e:
print(f"ERROR: API request failed: {e}", file=sys.stderr)
sys.exit(1)
result = resp.json()
if "choices" not in result:
print("ERROR: Unexpected API response:", file=sys.stderr)
print(result, file=sys.stderr)
sys.exit(1)
audios = result["choices"][0]["message"].get("audio", [])
if not audios:
print("ERROR: No audio in response.", file=sys.stderr)
print(result, file=sys.stderr)
sys.exit(1)
save_outputs(audios, output)
metadata = result["choices"][0]["message"].get("content", "")
if metadata:
print(f"\n📋 Metadata:\n{metadata}", file=sys.stderr)
if __name__ == "__main__":
main()
#!/usr/bin/env bash
# ACE-Step Music Generation via API
# Usage: generate.sh <prompt> [options]
# Options: --lyrics "..." --duration 30 --language en --instrumental --output file.mp3
# --bpm 120 --key "C major" --seed 42 --sample-mode --batch 2
set -euo pipefail
# Config
API_KEY="${ACE_MUSIC_API_KEY:-}"
BASE_URL="${ACE_MUSIC_BASE_URL:-https://api.acemusic.ai}"
OUTPUT="output_$(date +%s).mp3"
# Defaults
DURATION=""
LANGUAGE="en"
INSTRUMENTAL="null"
BPM=""
KEY_SCALE=""
SEED=""
SAMPLE_MODE="false"
BATCH_SIZE=1
LYRICS=""
PROMPT=""
FORMAT="mp3"
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--lyrics) LYRICS="$2"; shift 2 ;;
--duration) DURATION="$2"; shift 2 ;;
--language) LANGUAGE="$2"; shift 2 ;;
--instrumental) INSTRUMENTAL="true"; shift ;;
--output|-o) OUTPUT="$2"; shift 2 ;;
--bpm) BPM="$2"; shift 2 ;;
--key) KEY_SCALE="$2"; shift 2 ;;
--seed) SEED="$2"; shift 2 ;;
--sample-mode) SAMPLE_MODE="true"; shift ;;
--batch) BATCH_SIZE="$2"; shift 2 ;;
--format) FORMAT="$2"; shift 2 ;;
*) PROMPT="$1"; shift ;;
esac
done
if [[ -z "$API_KEY" ]]; then
echo "ERROR: ACE_MUSIC_API_KEY not set." >&2
echo "Get your free API key at: https://acemusic.ai/playground/api-key" >&2
exit 1
fi
if [[ -z "$PROMPT" && "$SAMPLE_MODE" == "false" ]]; then
echo "Usage: generate.sh <prompt> [--lyrics '...'] [--duration 30] [--language en] [--instrumental] [--output file.mp3]" >&2
exit 1
fi
# Build audio_config
AUDIO_CONFIG="{\"vocal_language\":\"$LANGUAGE\",\"format\":\"$FORMAT\""
[[ -n "$DURATION" ]] && AUDIO_CONFIG="$AUDIO_CONFIG,\"duration\":$DURATION"
[[ -n "$BPM" ]] && AUDIO_CONFIG="$AUDIO_CONFIG,\"bpm\":$BPM"
[[ "$INSTRUMENTAL" != "null" ]] && AUDIO_CONFIG="$AUDIO_CONFIG,\"instrumental\":$INSTRUMENTAL"
[[ -n "$KEY_SCALE" ]] && AUDIO_CONFIG="$AUDIO_CONFIG,\"key_scale\":\"$KEY_SCALE\""
AUDIO_CONFIG="$AUDIO_CONFIG}"
# Build message content
if [[ -n "$LYRICS" && -n "$PROMPT" ]]; then
# Tagged mode
CONTENT="<prompt>${PROMPT}</prompt>\n<lyrics>${LYRICS}</lyrics>"
elif [[ -n "$LYRICS" ]]; then
CONTENT="$LYRICS"
else
CONTENT="$PROMPT"
fi
# Escape for JSON
CONTENT_ESCAPED=$(echo -n "$CONTENT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" | sed 's/^"//;s/"$//')
# Build request body
BODY="{\"messages\":[{\"role\":\"user\",\"content\":\"$CONTENT_ESCAPED\"}],\"audio_config\":$AUDIO_CONFIG,\"stream\":false"
[[ -n "$LYRICS" && -n "$PROMPT" ]] || true # tagged mode handled via content
[[ "$SAMPLE_MODE" == "true" ]] && BODY="$BODY,\"sample_mode\":true"
[[ -n "$SEED" ]] && BODY="$BODY,\"seed\":$SEED"
[[ "$BATCH_SIZE" -gt 1 ]] && BODY="$BODY,\"batch_size\":$BATCH_SIZE"
BODY="$BODY}"
echo "🎵 Generating music..." >&2
echo " Prompt: ${PROMPT:-[lyrics/sample mode]}" >&2
[[ -n "$DURATION" ]] && echo " Duration: ${DURATION}s" >&2
echo " Language: $LANGUAGE" >&2
# API call
RESPONSE=$(curl -s -X POST "${BASE_URL}/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "$BODY")
# Check for errors
if echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if 'choices' in d else 1)" 2>/dev/null; then
# Extract metadata
METADATA=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['choices'][0]['message'].get('content',''))" 2>/dev/null || echo "")
# Extract and save audio(s)
COUNT=$(echo "$RESPONSE" | python3 -c "
import sys,json
d=json.load(sys.stdin)
audios=d['choices'][0]['message'].get('audio',[])
print(len(audios))
" 2>/dev/null || echo "0")
if [[ "$COUNT" -eq 0 ]]; then
echo "ERROR: No audio in response" >&2
echo "$RESPONSE" >&2
exit 1
fi
echo "$RESPONSE" | python3 -c "
import sys, json, base64
d = json.load(sys.stdin)
audios = d['choices'][0]['message'].get('audio', [])
output = '$OUTPUT'
for i, a in enumerate(audios):
url = a['audio_url']['url']
b64 = url.split(',', 1)[1]
fname = output if len(audios) == 1 else output.rsplit('.', 1)[0] + f'_{i+1}.' + output.rsplit('.', 1)[1]
with open(fname, 'wb') as f:
f.write(base64.b64decode(b64))
print(f'Saved: {fname}', file=sys.stderr)
print(fname)
"
if [[ -n "$METADATA" ]]; then
echo "" >&2
echo "📋 Metadata:" >&2
echo "$METADATA" >&2
fi
else
echo "ERROR: API request failed" >&2
echo "$RESPONSE" >&2
exit 1
fi