Skip to content

Latest commit

 

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sonauto — AI Music Generation Interface

Sonauto — Sonata, Automated.

A full-stack AI music generation SaaS. Describe your sound in plain words and walk away with a complete song — AI-written lyrics, an original melody, and unique cover artwork — all generated in minutes.

🎵 Live Demo  ·  Portfolio  ·  GitHub  ·  Twitter / X

Next.js 15 TypeScript Python 3.12 Modal Vercel License


📸 Screenshots

Light Mode Dark Mode
Sonauto Light Mode Sonauto Dark Mode

🎵 Live Demo

https://sonauto-three.vercel.app

Sign up for free and receive 10 credits (5 songs) immediately — no credit card required.
The app is a portfolio project. Payments run in Polar sandbox mode (test cards accepted, no real charges).


Table of Contents


🔭 Overview

Sonauto is an end-to-end AI music generation platform built as a production-grade portfolio project. A user describes a song — a genre, a mood, a story — and the system orchestrates three AI models in sequence to produce a fully finished track: structured lyrics, a synthesized audio file, and a matching piece of cover art.

The platform is split into two independently deployable units:

  • web-app — A Next.js 15 application (App Router) handling auth, the UI, background job dispatch, and all user-facing features.
  • modal-workers — A Python 3.12 Modal serverless worker that loads ACE-Step (audio generation) and SDXL-Turbo (thumbnail generation) onto GPU and exposes a single FastAPI-style endpoint that the web app calls.

The two services communicate over HTTP. The web app fires an Inngest background function which calls the Modal endpoint; Modal uploads the finished audio and thumbnail directly to Cloudflare R2 and returns the public URLs.


✨ Features

Music Generation

  • Four generation modes — Simple (vibe-only), Custom Auto (AI lyrics from theme), Custom Manual (bring your own lyrics), and Instrumental (no lyrics at all)
  • AI-structured lyrics — Verses, choruses, bridges, intros, and outros scaled to the chosen song duration (15 s – 3 min)
  • Controllable duration — Slider from 15 to 180 seconds in 15-second steps
  • Seed control — Advanced option to lock in a specific generation seed for reproducible results
  • Style tags — Genre, vocal type, instruments, mood, and BPM tags are auto-generated from the user's description and fed directly into ACE-Step
  • Indian subcontinent support — Ghazal, Sufi, Bollywood, Qawwali, sitar, tabla, and related genres are explicitly supported in all prompts

Track Management

  • Rename tracks inline
  • Publish / Unpublish — toggle community visibility
  • Delete with smart refund logic (see Credit System)
  • Download as a high-quality WAV via time-limited, cryptographically signed R2 pre-signed URLs
  • View lyrics with a one-click copy-to-clipboard action

Audio Player

  • Persistent floating player — survives page navigation within the app
  • Seek bar, volume control, play/pause
  • Sidebar-aware positioning (shifts right when sidebar is open)
  • Download and lyrics access from the player itself

Community

  • Published tracks are visible to all users
  • Unique listen count (once per user per track)
  • Like / unlike tracks
  • Category-based browsing (3 AI-generated tags per song)

UX & Polish

  • Full light/dark theme with system preference detection
  • Skeleton loading states on all async content
  • Toast notifications for all async actions (Sonner)
  • Responsive across mobile, tablet, and desktop
  • Breadcrumb navigation in the protected app header
  • Frosted-glass sticky headers
  • Custom audio equalizer and skeleton shimmer CSS animations

🤖 AI Models & Services

Layer Model / Service Purpose
Text Groqopenai/gpt-oss-120b (with automatic fallback) Generates style tags, lyrics, song title, and 3 genre categories from the user's description. Runs in parallel where possible.
Audio ACE-Step Open-source music generation model. Takes a comma-separated style/tag prompt and structured lyrics and synthesises a full audio waveform. Runs on Modal (NVIDIA L4 GPU).
Artwork SDXL-Turbo (stabilityai/sdxl-turbo) Generates abstract, atmospheric album cover art from the style prompt. 2-step inference for fast generation. Runs on the same Modal container as ACE-Step.

Groq Text Pipeline

Every generation runs four Groq calls (some in parallel). Model selection goes through a small preference-ordered fallback chain (openai/gpt-oss-120bopenai/gpt-oss-20bllama-3.1-8b-instant) so a Groq model deprecation degrades gracefully instead of breaking generation — see web-app/lib/groq.ts.

  1. generateTags — converts the user description into ACE-Step's comma-separated tag format ("pop, upbeat, female vocal, acoustic guitar, 120 bpm")
  2. generateLyrics — writes full, duration-scaled lyrics with [verse], [chorus], [bridge], [intro], [outro] markers (skipped for instrumental)
  3. generateTitle — produces a short, punchy 2–5 word song title
  4. extractCategories — returns exactly 3 genre/mood category tags as JSON for community browsing

🛠 Tech Stack

Web App

Category Technology
Framework Next.js 15 — App Router, React Server Components, Server Actions
Language TypeScript 5.8
Styling Tailwind CSS v4
UI Components shadcn/ui (Radix UI primitives)
Authentication Better Auth (email + password, session cookies)
Auth UI @daveyplate/better-auth-ui
Database ORM Prisma 6
Database Neon — serverless PostgreSQL (connection pooling + direct URL)
Env Validation @t3-oss/env-nextjs (T3 App Stack)
Background Jobs Inngest — event-driven, retryable job queue
File Storage Cloudflare R2 via AWS S3-compatible SDK
Payments Polar — one-time credit packs (sandbox)
AI SDK Vercel AI SDK + @ai-sdk/groq
State Management Zustand (audio player global state)
Notifications Sonner
Icons Lucide React
Theming next-themes
Font Libre Franklin (Google Fonts, variable)

Modal Workers

Category Technology
Runtime Modal — serverless GPU compute
Language Python 3.12.3
GPU NVIDIA L4 (per generation)
Audio Model ACE-Step (cloned from GitHub, pinned to 1bee4c9)
Image Model SDXL-Turbo via Diffusers
Audio I/O torchaudio + soundfile (WAV output)
Storage Upload boto3 (S3-compatible Cloudflare R2)
API Framework Modal's built-in FastAPI endpoint (@modal.fastapi_endpoint)
Model Cache Modal persistent Volume (sonauto-models-cache) — avoids re-downloading on cold start
Secrets Modal named secrets (sonauto-secret)

🏗 Architecture

User Browser
     │
     ▼
Next.js App (Vercel)
     │
     ├─ Better Auth ──────────────── Neon PostgreSQL (Prisma)
     │
     ├─ /api/generate (POST)
     │       │
     │       ├─ Rate limit check (GenerationEvent count, rolling 24h window)
     │       ├─ Atomic credit deduction (updateMany WHERE credits >= 2)
     │       ├─ Song & GenerationEvent records created atomically
     │       └─ inngest.send("song/generate")
     │                   │
     │                   ▼
     │             Inngest Worker (background, retryable)
     │                   │
     │                   ├─ Step 1: mark song "generating"
     │                   ├─ Step 2: Groq — tags + lyrics + title + categories (parallel)
     │                   ├─ Step 3: save text content to DB
     │                   ├─ Step 4: step.fetch → Modal GPU endpoint
     │                   │               │
     │                   │               ├─ SDXL-Turbo → thumbnail → R2
     │                   │               └─ ACE-Step   → audio WAV → R2
     │                   └─ Step 5: save URLs, mark "completed"
     │
     ├─ Cloudflare R2 ─────────────── audio/*.wav + thumbnails/*.webp
     │
     └─ Polar (Sandbox) ────────────── Webhook → credit increment on order paid

The client polls for completion every 5 seconds via router.refresh() on the TracksFetcher Server Component. A 15-minute stale-song remediation pass runs on each poll tick and automatically refunds credits for jobs that never completed.


📁 Project Structure (Tentative!)

sonauto-ai-music-gen-saas/
│
├── web-app/                          # Next.js 15 application
│   ├── prisma/
│   │   └── schema.prisma             # Database schema
│   ├── public/
│   │   └── images/                   # Hero banners, favicon
│   └── src/
│       ├── app/
│       │   ├── (auth)/               # Auth route group
│       │   │   └── auth/
│       │   │       ├── layout.tsx    # Split-panel auth layout (branding + form)
│       │   │       └── [...all]/     # Better Auth UI catch-all
│       │   ├── (protected)/          # Protected route group
│       │   │   ├── layout.tsx        # Sidebar + header + audio player shell
│       │   │   ├── dashboard/        # User's track library
│       │   │   ├── generate/         # Generation panel + track feed
│       │   │   ├── billing/          # Credit purchase page
│       │   │   └── account/          # Account settings
│       │   ├── api/
│       │   │   ├── generate/
│       │   │   │   └── route.ts      # POST — rate limit, credit deduction, job dispatch
│       │   │   ├── auth/             # Better Auth handler
│       │   │   └── inngest/          # Inngest event handler
│       │   ├── layout.tsx            # Root layout (fonts, theme, providers)
│       │   ├── page.tsx              # Landing page
│       │   └── not-found.tsx         # Global 404
│       ├── components/
│       │   ├── audio-player/         # Floating persistent audio player
│       │   ├── landing/              # Hero, Features, HowItWorks, Pricing, Header, Footer
│       │   ├── tracks/               # TrackGenPanel, TracksFetcher, Tracks, TrackThumbnail
│       │   ├── theme/                # ThemeProvider, ThemeToggle
│       │   ├── auth/                 # Providers, SignOutClient
│       │   ├── ui/                   # shadcn/ui primitives
│       │   ├── AppHeader.tsx         # Sticky header with rate-limit badge
│       │   ├── AppSidebar.tsx        # Navigation + credit display + upgrade CTA
│       │   ├── AppBreadcrumbs.tsx    # Dynamic breadcrumbs from pathname
│       │   └── Logo.tsx
│       ├── inngest/
│       │   ├── client.ts             # Inngest client
│       │   └── functions.ts          # generateSong function (5-step pipeline)
│       ├── lib/
│       │   ├── constants.ts          # App-wide constants (routes, plans, DAILY_GENERATION_LIMIT)
│       │   ├── groq.ts               # Groq AI helpers (generateTags, generateLyrics, etc.)
│       │   ├── prompts.ts            # System prompts for all Groq calls
│       │   └── utils.ts
│       ├── server/
│       │   ├── actions/
│       │   │   └── songs.ts          # Server actions: rename, delete, publish, download, like
│       │   ├── better-auth/
│       │   │   ├── config.ts         # Better Auth + Polar plugin config
│       │   │   ├── server.ts         # getSession helper
│       │   │   └── client.ts         # authClient (React)
│       │   └── db.ts                 # Prisma client singleton
│       ├── stores/
│       │   └── useAudioPlayerStore.ts # Zustand audio player store
│       ├── styles/
│       │   └── globals.css           # Tailwind v4 theme tokens, skeleton, scrollbar
│       ├── env.js                    # T3 env validation schema
│       └── middleware.ts             # Edge auth middleware (optimistic cookie check)
│
├── modal-workers/                    # Python GPU workers
│   ├── main.py                       # SongGenServer: ACE-Step + SDXL-Turbo + R2 upload
│   ├── requirements.txt              # boto3, pydantic, transformers, python-dotenv
│   └── .env                          # Local secrets for modal serve/test
│
├── package.json                      # Monorepo root (pnpm workspaces)
└── README.md

🔒 Security & Rate Limiting

Email Verification

All new accounts must verify their email address before they can sign in. This prevents throwaway or fake emails from consuming free Modal GPU credits and Cloudflare R2 storage.

How it works:

  1. A user signs up with email + password.
  2. Better Auth immediately fires a sendVerificationEmail callback, which dispatches a branded HTML email via Nodemailer + Gmail SMTP (using a Google App Password — not the account password).
  3. The email contains a signed, time-limited verification link generated by Better Auth.
  4. Until the link is clicked, the account exists in the database but emailVerified is false and all sign-in attempts are rejected with a 403.
  5. On click, emailVerified is set to true and the user is auto-signed in (autoSignInAfterVerification: true).
  6. If a user with an unverified email attempts to sign in, a fresh verification email is re-sent automatically (sendOnSignIn: true).

Implementation notes:

  • Email transport: nodemailer@8.0.5 (pinned exact version, zero transitive dependencies) over Gmail SMTP with an App Password.
  • No custom domain required — Gmail's SMTP relay is used directly.
  • The email is sent with void (fire-and-forget) to avoid timing attacks and keep the sign-up response fast on Vercel serverless.
  • The Verification model was already present in the Prisma schema — no migration was needed.
  • The existing authViewPaths catch-all in app/(auth)/auth/[...all]/page.tsx already handles the verify-email path via @daveyplate/better-auth-ui.
  • The logo in the verification email is a PNG (/public/images/logo-email.png, 192×192 px, displayed at 64×64).

To generate a Google App Password: Google Account → Security → 2-Step Verification → App Passwords → create one named "Sonauto".

Daily Generation Limit — Rate-Limit Badge

When a user exhausts their {DAILY_GENERATION_LIMIT} daily generations, a compact amber badge (icon + "Limit") appears in the sticky app header. The badge is intentionally terse to avoid crowding the header on small screens — the full context ("You've used all N daily generations. Resets at HH:MM.") is surfaced via a shadcn/ui Tooltip on hover/focus.

How it computes the reset time:

The layout fetches the oldest generation event from an immutable GenerationEvent ledger within the rolling 24-hour window and adds 24 hours to its createdAt timestamp. This gives the exact moment the window slides enough to allow a new generation, formatted in the user's local timezone by toLocaleTimeString. Because it queries an append-only ledger rather than the user's active song list, the rate limit calculation cannot be spoofed by deleting tracks.

Router cache caveat:

Next.js maintains a client-side router cache (RSC payload cache) in browser memory. A Vercel redeploy does not flush this cache. In the unlikely event that a user navigates back to the app immediately after a redeploy, they may briefly see a stale layout where the badge is absent even though the limit has been reached. This is self-correcting: any call to router.refresh() — which is triggered by every generate attempt (success, 429, or error) — invalidates the cache and re-fetches the server layout with accurate data. No data is lost and the API-level rate-limit check (POST /api/generate) remains authoritative regardless of the UI state.

Daily Generation Limit

Each authenticated user is limited to 2 song generations per rolling 24-hour window.

To prevent exploitation (where a user might generate, download, delete the track, and regenerate endlessly), the rate limit is completely decoupled from the actual Song records. Instead, it checks an immutable GenerationEvent ledger. This is enforced server-side in POST /api/generate before any credits are touched:

// src/app/api/generate/route.ts
const since = new Date(Date.now() - 24 * 60 * 60 * 1000);
const todayCount = await db.generationEvent.count({
  where: { userId: session.user.id, createdAt: { gte: since } },
});

if (todayCount >= DAILY_GENERATION_LIMIT) {
  return NextResponse.json(
    { error: "You've reached your daily limit of 2 generations..." },
    { status: 429, headers: { "Retry-After": "86400" } },
  );
}

When a user successfully initiates a generation, a Song and a GenerationEvent are created atomically via a Prisma $transaction.

Important Limitation Design

If a user manually cancels a queued song (which correctly refunds their 2 credits), that action does not erase the GenerationEvent. This intentionally burns one of their daily generation attempts, actively preventing malicious users from spamming and cancelling the compute queue while dodging rate limits.

The limit is configured via a single constant — DAILY_GENERATION_LIMIT in src/lib/constants.ts — so it can be updated in one place.

Atomic Credit Deduction

Credits are deducted with a WHERE credits >= 2 guard in a single updateMany call — not a read-then-write. This prevents double-spending under concurrent requests:

const deducted = await db.user.updateMany({
  where: { id: session.user.id, credits: { gte: 2 } },
  data: { credits: { decrement: 2 } },
});
if (deducted.count === 0)
  return NextResponse.json({ error: "No credits" }, { status: 402 });

Smart Credit Refunds

The delete/failure refund policy is intentionally asymmetric:

Song Status at Deletion / Failure Refund
queued — not yet picked up by GPU ✅ 2 credits refunded
generating — GPU is actively running ❌ No refund (GPU time is consumed)
failed — already refunded by Inngest onFailure ❌ No double-refund
completed — user received their track ❌ No refund

Stale Song Remediation

Songs stuck in queued or generating for more than 15 minutes (Modal's max timeout) are automatically marked failed and their credits refunded by TracksFetcher on every poll tick — without a separate cron job.

Secure Downloads

Audio downloads use cryptographically signed R2 pre-signed URLs that expire in 60 seconds. Authorization is enforced server-side: only the track owner or users viewing a published track can request a URL.

Edge Auth Middleware

middleware.ts performs an optimistic session cookie check at the edge to redirect unauthenticated users from protected routes instantly — before any DB query is made.

Modal Proxy Auth

The Modal endpoint requires Modal-Key and Modal-Secret headers (requires_proxy_auth=True). These are never exposed to the client.


💳 Credit & Billing System

Each song generation costs 2 credits. Credits are purchased as one-time packs via Polar (sandbox mode for this portfolio project):

Pack Price Credits Songs
Free (signup bonus) $0 10 ~5
Starter Pack $5 60 ~30
Producer Pack $12 160 ~80
Studio Pack $20 300 ~150

Credits are added via a Polar webhook (onOrderPaid) that reads credits_to_add from the product metadata and increments the user's balance in the DB. Credits never expire.


🚀 Installation & Local Development

Prerequisites

Tool Version
Node.js ≥ 20
pnpm 10.33.0 (see packageManager in web-app/package.json)
Python 3.12.3
Modal CLI latest (pip install modal)
Prisma CLI bundled via pnpm postinstall

You will also need accounts with:


1. Clone the Repository

git clone https://github.com/KeepSerene/sonauto-ai-music-gen-saas.git
cd sonauto-ai-music-gen-saas

2. Web App Setup

cd web-app

# Install dependencies
pnpm install

# Copy the example env file
cp .env.example .env
# → Fill in all values (see Environment Variables section below)

# Generate Prisma client and push schema to your Neon DB
pnpm db:push

# (Optional) Open Prisma Studio to inspect your database
pnpm db:studio

3. Modal Workers Setup

cd ../modal-workers

# Create and activate a Python 3.12.3 virtual environment
python3.12 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

# Install Python dependencies
pip install -r requirements.txt
pip install modal                # Modal CLI

# Authenticate with Modal
modal setup

# Create the Modal named secret (add your R2 credentials in the Modal dashboard)
# Required keys: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY,
#                R2_BUCKET_NAME, R2_PUBLIC_URL
# https://modal.com/secrets → New Secret → name it "sonauto-secret"

# Serve the worker locally (for testing; gives you a temporary web URL)
modal serve main.py

# Or deploy to Modal permanently and get a stable endpoint URL
modal deploy main.py
# → Copy the endpoint URL into MODAL_API_URL in web-app/.env

4. Run Locally

In one terminal (web app):

cd web-app
pnpm dev          # Starts Next.js on http://localhost:3000 with Turbopack

In a second terminal (Inngest dev server — required for background jobs):

cd web-app
npx inngest-cli@latest dev   # Connects to your /api/inngest endpoint

Tip: Set INNGEST_DEV="1" in your .env to tell Inngest to use the local dev server instead of the cloud.

To expose your local server to Polar webhooks during development:

cd web-app
pnpm tunnel      # Runs ngrok on port 3000
# → Use the ngrok URL as your Polar webhook endpoint

🔑 Environment Variables

Web App (web-app/.env) {#web-app-env}

# ── Node ──────────────────────────────────────────────────────────────────────
NODE_ENV="development"

# ── Better Auth ───────────────────────────────────────────────────────────────
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=""
# Base URL of your running app (no trailing slash)
BETTER_AUTH_URL="http://localhost:3000"

# ── Neon PostgreSQL (Prisma) ──────────────────────────────────────────────────
# Pooled connection (for runtime queries)
DATABASE_URL=""
# Direct connection (for Prisma migrations — no -pooler suffix)
DIRECT_URL=""

# ── Modal GPU Workers ─────────────────────────────────────────────────────────
# From: Modal Dashboard → Settings → API Tokens
MODAL_API_KEY=""
MODAL_API_SECRET=""
# Web endpoint URL from `modal serve/deploy main.py`
MODAL_API_URL=""

# ── Groq AI ───────────────────────────────────────────────────────────────────
# From: https://console.groq.com/keys
GROQ_API_KEY=""

# ── Inngest ───────────────────────────────────────────────────────────────────
INNGEST_DEV="1"              # Remove in production
INNGEST_EVENT_KEY=""         # Auto-added by Inngest's Vercel integration
INNGEST_SIGNING_KEY=""       # Auto-added by Inngest's Vercel integration

# ── Cloudflare R2 ─────────────────────────────────────────────────────────────
R2_ACCOUNT_ID=""
R2_ACCESS_KEY_ID=""
R2_SECRET_ACCESS_KEY=""
R2_BUCKET_NAME=""

# ── Polar (Sandbox) ───────────────────────────────────────────────────────────
# From: https://sandbox.polar.sh/dashboard → Settings → API
POLAR_ACCESS_TOKEN=""
POLAR_WEBHOOK_SECRET=""

# ── Public ────────────────────────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL="http://localhost:3000"

# ── Google Account ────────────────────────────────────────────────────────────────────
GMAIL_USER="your.email@gmail.com"
GMAIL_APP_PASSWORD="xxxx xxxx xxxx xxxx"   # Google App Password (not your Gmail password)

Modal Workers (modal-workers/.env) {#modal-workers-env}

These are stored as a Modal named secret (sonauto-secret) in the Modal dashboard, not in a local .env file in production. For local testing with modal serve, you can use a .env:

# Cloudflare R2 — same account as the web app
R2_ACCOUNT_ID=""
R2_ACCESS_KEY_ID=""
R2_SECRET_ACCESS_KEY=""
R2_BUCKET_NAME=""
# The public R2 custom domain or dev URL (no trailing slash)
# e.g. https://pub-xxxxxxxxxxxx.r2.dev
R2_PUBLIC_URL=""

Note: Add all five keys to a Modal secret named exactly sonauto-secret at modal.com/secrets. The main.py worker references them by that name.


🗄 Database Setup

The schema is managed by Prisma and hosted on Neon. Key models:

Model Purpose
User Auth identity + credit balance
Song Track record (status, URLs, lyrics, prompt, mode)
Category Deduplicated genre/mood tags (many-to-many with Song)
Like Unique user–song like join table
Listen Unique user–song listen join table
Session / Account / Verification Better Auth managed tables
# Push schema changes to the database (development)
pnpm db:push

# Generate and apply a migration (production-safe)
pnpm db:generate   # creates a migration file
pnpm db:migrate    # applies pending migrations (use in CI/CD)

☁ Deployment

Web App → Vercel

  1. Import the repository into Vercel and set the root directory to web-app
  2. Add all production environment variables (same keys as .env, without INNGEST_DEV)
  3. Install the Inngest Vercel integration — it auto-injects INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY
  4. Deploy — Vercel's build command runs prisma generate automatically via postinstall

Modal Workers → Modal Cloud

cd modal-workers
source .venv/bin/activate
modal deploy main.py
# → Outputs a stable HTTPS endpoint URL
# → Paste this URL into MODAL_API_URL in Vercel's environment variables

The Modal worker uses a persistent volume (sonauto-models-cache) to cache Hugging Face model weights. The first cold start downloads the models; all subsequent starts load from the volume in seconds.


🎛 Generation Modes

Mode Description Groq calls
Simple User writes a free-form vibe description. AI handles style tags, lyrics, title, and categories. generateTags + generateLyrics + generateTitle + extractCategories
Custom Auto User sets a theme/style and the genres field. AI writes matching lyrics for that specific style. Simple + Lyrics Description/Direction
Custom Manual User writes their own full lyrics. AI only generates style tags, title, and categories. generateTags + generateTitle + extractCategories
Instrumental Any mode with the Instrumental toggle on. Lyrics generation is skipped entirely. generateTags + generateTitle + extractCategories

👤 Author

Dhrubajyoti Bhattacharjee

A self-taught full-stack developer focused on building production-ready applications with modern web technologies and AI integrations.

🌐 Portfolio math-to-dev.vercel.app
🐙 GitHub @KeepSerene
💼 LinkedIn dhrubajyoti-bhattacharjee-320822318
🐦 X (Twitter) @UsualLearner

📄 License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.


Built with ♪ by KeepSerene

About

Sonauto — A full-stack AI music generation SaaS. Describe your sound in plain words and walk away with a complete song — AI-written lyrics, an original melody, and unique cover artwork — all generated in minutes.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages