“””
ai_music_studio_singlefile.py
Single-file demo app (frontend + backend). Run this Python script and go to:
http://127.0.0.1:8000
It implements:
– Serves a single-page HTML UI (embedded below)
– /api/generate -> starts a dummy generation job and returns job_id
– /api/status/ -> returns {status, progress}
– /api/result/ -> returns generated WAV when ready
– /api/vocal-remove -> demo endpoint (returns same file as both vocal/instrumental)
– /api/upload-voice -> demo (stores sample and returns a voice_id)
How to run:
1) Install Python 3.10+ and pip
2) pip install flask numpy soundfile
3) python ai_music_studio_singlefile.py
4) Open http://127.0.0.1:8000 in your browser
IMPORTANT:
– This demo produces synthetic sine-wave audio as a placeholder.
– To integrate real models (MusicGen/StableAudio), replace the function `do_real_generation(…)`
with your PyTorch model inference code. That code will require GPU, PyTorch, audiocraft, etc.
“””
import os
import io
import uuid
import time
import threading
from typing import Dict
from flask import Flask, request, send_file, jsonify, render_template_string, abort
import numpy as np
import soundfile as sf
app = Flask(__name__)
# allow file sending from same server (CORS not needed for local)
# Simple in-memory jobs store for demo
JOB_STORE: Dict[str, Dict] = {}
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), “outputs_singlefile”)
os.makedirs(OUTPUT_DIR, exist_ok=True)
# —————————–
# HTML page embedded as string
# —————————–
INDEX_HTML = r”””
AI Music Studio — Single-file Demo
MG
AI Music Studio — Single-file Demo
Local demo: text → music (sine wave). Replace server code with MusicGen for real audio.
Credits: 5
Studio
Generation progress
Status: idle
Job: —
Preview / Player
Library
Saved generations
Tools
“””
# —————————–
# Utility: create a sine WAV (mono) into a file path
# —————————–
def synthesize_sine_wav(path: str, duration_seconds: int = 10, frequency: float = 440.0, sr: int = 32000):
“””Create a mono WAV file with a sine wave. Uses soundfile (pysoundfile).”””
t = np.linspace(0, duration_seconds, int(sr * duration_seconds), endpoint=False)
audio = 0.3 * np.sin(2 * np.pi * frequency * t)
sf.write(path, audio, sr, subtype=’PCM_16′)
# —————————–
# Dummy “model” generator (runs in worker thread)
# —————————–
def worker_generate(job_id: str, prompt: str, length_seconds: int = 30, genre: str = None, mood: str = None, voice_id: str = None):
“””
Simulate generation progress and create a WAV file.
Replace the contents of this function with real model inference for real audio.
“””
JOB_STORE[job_id][“status”] = “running”
# Simulate progressive updates
for p in range(0, 90, 10):
JOB_STORE[job_id][“progress”] = p
time.sleep(0.45)
# Create a filename
fname = f”generation_{job_id}.wav”
out_path = os.path.join(OUTPUT_DIR, fname)
# Choose a pseudo-frequency influenced by prompt length so files sound slightly different
freq = 220 + (len(prompt) % 200)
synthesize_sine_wav(out_path, duration_seconds=length_seconds, frequency=freq, sr=32000)
JOB_STORE[job_id][“progress”] = 100
JOB_STORE[job_id][“status”] = “complete”
JOB_STORE[job_id][“output_path”] = out_path
JOB_STORE[job_id][“finished_at”] = time.time()
print(f”[worker] job {job_id} done -> {out_path}”)
# —————————–
# PLACEHOLDER for real generation (example instructions)
# —————————–
def do_real_generation_placeholder(job_id: str, prompt: str, length_seconds: int = 30, genre: str = None, mood: str = None, voice_id: str = None):
“””
THIS FUNCTION IS A COMMENTED GUIDE for how to replace the dummy worker with real inference.
DO NOT CALL THIS FUNCTION AS-IS from the demo; instead, copy/replace worker_generate with code like:
from audiocraft.models import MusicGen
import torch
import soundfile as sf
model = MusicGen.get_pretrained(‘facebook/musicgen-small’)
model.to(‘cuda’) # requires GPU and proper PyTorch+CUDA install
JOB_STORE[job_id][‘status’] = ‘running’
# If model provides callbacks for progress, update JOB_STORE periodically
wav = model.generate([prompt], duration=length_seconds, progress=True) # pseudo-API
out = os.path.join(OUTPUT_DIR, f’generation_{job_id}.wav’)
sf.write(out, wav[0].cpu().numpy(), samplerate) # depends on model output type
JOB_STORE[job_id][‘status’] = ‘complete’
JOB_STORE[job_id][‘output_path’] = out
NOTE: Real integration depends on the model’s exact API/version. MusicGen/Audiocraft APIs change across releases.
“””
raise NotImplementedError(“This is a placeholder. See the docstring for integration instructions.”)
# —————————–
# Flask routes
# —————————–
@app.route(“/”)
def index():
return render_template_string(INDEX_HTML)
@app.route(“/api/generate”, methods=[“POST”])
def api_generate():
prompt = request.form.get(“prompt”, “”)
genre = request.form.get(“genre”)
length = int(request.form.get(“length”, 30))
mood = request.form.get(“mood”)
voice_id = request.form.get(“voice_id”)
if not prompt:
return jsonify({“error”: “prompt required”}), 400
job_id = uuid.uuid4().hex
JOB_STORE[job_id] = {“status”: “queued”, “progress”: 0, “prompt”: prompt, “created_at”: time.time()}
# Start worker thread (demo). In production use a queue (Redis + worker).
t = threading.Thread(target=worker_generate, args=(job_id, prompt, length, genre, mood, voice_id), daemon=True)
t.start()
return jsonify({“job_id”: job_id})
@app.route(“/api/status/“, methods=[“GET”])
def api_status(job_id):
job = JOB_STORE.get(job_id)
if not job:
return jsonify({“error”: “job not found”}), 404
return jsonify({“status”: job.get(“status”, “queued”), “progress”: int(job.get(“progress”, 0))})
@app.route(“/api/result/“, methods=[“GET”])
def api_result(job_id):
job = JOB_STORE.get(job_id)
if not job:
return jsonify({“error”: “job not found”}), 404
if job.get(“status”) != “complete”:
return jsonify({“error”: “not ready”}), 400
path = job.get(“output_path”)
if not path or not os.path.exists(path):
return jsonify({“error”: “output missing”}), 404
# send file
return send_file(path, mimetype=”audio/wav”, as_attachment=True, download_name=os.path.basename(path))
@app.route(“/api/vocal-remove”, methods=[“POST”])
def api_vocal_remove():
“””
Demo vocal removal: store uploaded file and return URLs for ‘instrumental’ and ‘vocals’.
Real implementation: run Demucs / Spleeter, store outputs in S3, return public/presigned URLs.
“””
f = request.files.get(“file”)
if not f:
return jsonify({“error”: “file required”}), 400
# store upload
uid = uuid.uuid4().hex
upl_path = os.path.join(OUTPUT_DIR, f”uploaded_{uid}.wav”)
f.save(upl_path)
# For demo we return the same file as both outputs; frontend expects URLs.
# We’ll serve via /api/raw/
fname = os.path.basename(upl_path)
return jsonify({
“instrumental_url”: f”/api/raw/{fname}”,
“vocals_url”: f”/api/raw/{fname}”
})
@app.route(“/api/upload-voice”, methods=[“POST”])
def api_upload_voice():
f = request.files.get(“file”)
if not f:
return jsonify({“error”: “file required”}), 400
vid = uuid.uuid4().hex
path = os.path.join(OUTPUT_DIR, f”voice_{vid}.wav”)
f.save(path)
return jsonify({“voice_id”: vid, “file”: f”/api/raw/{os.path.basename(path)}”})
@app.route(“/api/raw/“)
def api_raw(filename):
path = os.path.join(OUTPUT_DIR, filename)
if not os.path.exists(path):
return abort(404)
# serve file
return send_file(path, mimetype=”audio/wav”)
# —————————–
# Run server
# —————————–
if __name__ == “__main__”:
import argparse
parser = argparse.ArgumentParser(description=”Single-file AI Music Studio demo server”)
parser.add_argument(“–host”, default=”127.0.0.1″, help=”Host to bind to”)
parser.add_argument(“–port”, type=int, default=8000, help=”Port to bind to”)
args = parser.parse_args()
print(f”Starting server at http://{args.host}:{args.port} — outputs dir: {OUTPUT_DIR}”)
app.run(host=args.host, port=args.port, debug=True)