# Soul Forge — AI Band Identity Generator

> Forge a complete band identity from a single audio track. Upload music, get back a band name, members, philosophy, trading card, portrait, and lore.

## Quick Start

```
POST https://aetherwavestudio.com/api/band-generation
Content-Type: multipart/form-data
X-AW-Key: <your-api-key>
```

**Minimum request:** Just an audio file.

```python
import requests

resp = requests.post(
    "https://aetherwavestudio.com/api/band-generation",
    headers={"X-AW-Key": "YOUR_API_KEY"},
    files={"audio": open("track.mp3", "rb")},
    data={"mode": "explore"},
    timeout=120
)
band = resp.json()
print(band["winner"]["bandName"])
```

**Cost:** 50 credits per forge (free for Producer/Mogul/Ultimate plans)

---

## How It Works

1. **Upload audio** → Librosa analyzes tempo, key, energy, instruments, texture
2. **AI generates band identity** → Claude/GPT creates name, members, philosophy, lore, visual aesthetic
3. **AI generates portrait** → Grok Imagine creates band photo (up to 6 options)
4. **Select portrait → Render card** → SVG trading card rendered as PNG, saved to gallery

---

## Phase 1: Generate Band Identity

```
POST /api/band-generation
Content-Type: multipart/form-data
Auth: X-AW-Key header or session cookie
```

### Form Fields

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `audio` | file | **Yes** | — | Audio file (MP3, WAV, M4A, OGG) |
| `mode` | string | No | `explore` | `explore` (4 candidates, picks best) or `refine` (1 polished result) |
| `artistType` | string | No | `ensemble` | `ensemble` (band) or `solo` (solo artist) |
| `userBandName` | string | No | — | Desired band/artist name |
| `songName` | string | No | filename | Track title |
| `userGenre` | string | No | auto-detected | Genre hint (e.g. "synthwave", "jazz") |
| `memberCount` | string | No | AI decides | Number of band members (1-10) |
| `memberNames` | string | No | — | Comma-separated member names |
| `bandVibe` | string | No | — | Creative vision / concept |
| `lyricalThemes` | string | No | — | Lyrical themes |
| `userInfluences` | string | No | — | Musical influences (e.g. "Radiohead, Bjork") |
| `userImageUrl` | string | No | — | URL of your own band photo (skips AI portrait) |
| `vocalGender` | string | No | auto | `male` or `female` (omit for auto-detect) |
| `artStyle` | string | No | `realistic` | `realistic`, `anime`, or `abstract` |
| `cardTheme` | string | No | `dark` | `dark`, `light`, or `vibrant` |

### Response

**Important:** Uses chunked transfer encoding with keepalive spaces. Read the full response body, trim whitespace, then parse JSON.

```json
{
  "winner": {
    "bandName": "Neon Pulse",
    "songName": "Vapor Dreams",
    "genre": "Synthwave / Retrowave",
    "philosophy": "We are the signal in the static...",
    "bandConcept": "A collective born from the feedback loops...",
    "signatureSound": "Warm analog pads swimming beneath...",
    "band_identity": "What makes Neon Pulse unique is...",
    "band_motto": "We are the frequency you forgot to tune into.",
    "influences": ["Kavinsky", "Perturbator", "Carpenter Brut"],
    "tags": ["neon", "retro-futurism", "analog-warmth", "nocturnal"],
    "members": [
      {
        "name": "Kira Volkov",
        "role": "Vocalist & Synth Lead",
        "archetype": "The Oracle",
        "background": "Former radio operator who heard music in static...",
        "personality": "Quietly intense, speaks in metaphors...",
        "appearance": "Silver-streaked dark hair, vintage aviator jacket..."
      }
    ],
    "world_building": {
      "origin_story": "In a basement studio beneath a defunct radio tower...",
      "fanbase_description": "Night-shift workers, coders, insomniacs...",
      "iconic_moment": "Their first live show was broadcast on a pirate..."
    },
    "sunoPrompt": "Driving synthwave with warm analog pads...",
    "imageUrl": "https://media.aetherwavestudio.com/..."
  },
  "alternatives": [
    {"bandName": "Glass Circuit", "genre": "Electronic", "philosophy": "..."},
    {"bandName": "The Midnight Signal", "genre": "Dark Synth", "philosophy": "..."}
  ],
  "portraitOptions": [
    "https://media.aetherwavestudio.com/portraits/opt1.png",
    "https://media.aetherwavestudio.com/portraits/opt2.png"
  ],
  "soulData": {
    "resonanceScore": 72,
    "viralPotential": 65,
    "mysteryScore": 48,
    "insight": "This track carries a melancholic optimism...",
    "famePoints": 48,
    "influencePoints": 22
  },
  "featuredTrackUrl": "https://media.aetherwavestudio.com/audio/...",
  "cardTheme": "dark",
  "artStyle": "realistic",
  "songName": "Vapor Dreams",
  "creditsDeducted": 50,
  "newBalance": 450,
  "userProvidedImage": false
}
```

### Generation Limits by Plan

| Plan | Max Bands |
|---|---|
| Free | 1 |
| Starter | 3 |
| Studio | 5 |
| Artist | 15 |
| Producer | 30 |
| Mogul/Ultimate | Unlimited |

---

## Phase 2: Render Trading Card

After Phase 1, the user selects a portrait from `portraitOptions`. Then render the final card:

```
POST /api/band-generation/render-card
Content-Type: application/json
Auth: X-AW-Key header or session cookie
```

### Request Body

```json
{
  "winner": { "...full band data from Phase 1..." },
  "soulData": { "...from Phase 1..." },
  "selectedPortraitUrl": "https://...chosen-portrait-url",
  "portraitOptions": ["url1", "url2"],
  "cardTheme": "dark",
  "artStyle": "realistic",
  "songName": "Vapor Dreams",
  "featuredTrackUrl": "https://...",
  "creditsDeducted": 50
}
```

### Response

```json
{
  "cardImageUrl": "https://media.aetherwavestudio.com/cards/front.png",
  "cardBackImageUrl": "https://media.aetherwavestudio.com/cards/back.png",
  "portraitUrl": "https://media.aetherwavestudio.com/portraits/final.png",
  "creationId": "uuid-string"
}
```

This automatically saves the band to the user's gallery as a `band_profile` creation.

---

## PDF Download

```
POST /api/band-profile-pdf
Content-Type: application/json
```

Request: `{ "bandData": { ...full band data... } }`
Response: PDF blob (application/pdf)

---

## Browser Automation Guide

For agents using browser automation (Playwright, Puppeteer, Claude-in-Chrome):

### Page URL
`https://aetherwavestudio.com/soul-forge`

### Step-by-Step Flow

1. **Navigate** to `/soul-forge`
2. **Upload audio** — Click the upload zone or drag-drop an audio file onto `#audioFile`
3. **Fill optional fields** (all in Panel 1 and Panel 2):
   - `#bandName` — Artist name
   - `#songName` — Song title
   - `#memberCount` — Number input (1-10)
   - `#genre` — Genre text
   - `#memberNames` — Comma-separated names
   - `#bandVibe` — Creative vision
   - `#lyricalThemes` — Lyrical themes
   - `#userInfluences` — Musical influences
   - Vocal gender toggle: 3 buttons in `.gender-selector` (values: `auto`, `male`, `female`)
   - `#mode` — Select: `explore` or `refine`
   - `#artistType` — Select: `ensemble` or `solo`
4. **Click "Forge My Artist"** button (`#generateBtn`)
5. **Wait for generation** — Progress steps animate over ~30-90 seconds. Watch for portrait options to appear
6. **Select portrait** — Click one of the `.portrait-option` images in the results panel
7. **Click "Forge Card"** button (`#forgeCardBtn`) — Renders the final trading card
8. **Results available:**
   - Card front image in `#resultCardFront`
   - Card back image in `#resultCardBack`
   - Download buttons for card, portrait, and PDF
   - "Go to Profile" button appears

### Authentication
- If not logged in, clicking "Forge My Artist" redirects to Google OAuth
- Form state is saved to `localStorage.soulForgeFormState` before redirect
- On return, form state is restored automatically

### Key Selectors
| Element | Selector | Purpose |
|---|---|---|
| Upload zone | `.upload-zone` | Click to open file picker |
| File input | `#audioFile` | Hidden file input |
| Generate button | `#generateBtn` | Starts generation |
| Portrait options | `.portrait-option` | Clickable portrait images (6 options) |
| Forge card button | `#forgeCardBtn` | Renders final trading card after portrait selection |
| Card front | `#resultCardFront` | Final card front image |
| Card back | `#resultCardBack` | Final card back image |
| Credit balance | `#creditBalance` | Nav credit display |

---

## Data Model

### Saved to `userCreations` table

| Column | Value |
|---|---|
| `type` | `band_profile` |
| `title` | Band name |
| `description` | Philosophy |
| `thumbnailUrl` | Card front image URL |
| `contentUrl` | Portrait URL |
| `creditsSpent` | 50 |
| `contentData` | Full band JSON (see below) |

### contentData JSONB

```json
{
  "bandName": "...",
  "genre": "...",
  "members": [{"name": "...", "role": "...", "archetype": "...", "background": "...", "personality": "...", "appearance": "..."}],
  "philosophy": "...",
  "bandIdentity": "...",
  "bandMotto": "...",
  "signatureSound": "...",
  "influences": ["..."],
  "tags": ["..."],
  "worldBuilding": {"origin_story": "...", "fanbase_description": "...", "iconic_moment": "..."},
  "cardImageUrl": "https://...",
  "cardBackImageUrl": "https://...",
  "portraitUrl": "https://...",
  "portraitOptions": ["url1", "url2"],
  "songName": "...",
  "songUrl": "https://...",
  "artStyle": "realistic",
  "cardTheme": "dark",
  "soulData": {"resonanceScore": 72, "viralPotential": 65, "mysteryScore": 48, "insight": "...", "famePoints": 48, "influencePoints": 22},
  "sunoPrompt": "..."
}
```

---

## Error Handling

| Error | Cause | Solution |
|---|---|---|
| 401 Unauthorized | No auth | Add `X-AW-Key` header or login via OAuth |
| 403 Band limit reached | Plan limit exceeded | Upgrade plan |
| 402 Insufficient credits | Balance < 50 | Buy more credits |
| 500 Audio analysis failed | Librosa/numpy not available | Ensure Python + librosa + numpy on server; LD_LIBRARY_PATH must include nix zlib |
| Timeout (>120s) | Long generation | Increase client timeout; generation can take 30-90s |

---

## Example: Full API Flow (Python)

```python
import requests
import json
import time

API = "https://aetherwavestudio.com"
KEY = "YOUR_API_KEY"
HEADERS = {"X-AW-Key": KEY}

# Phase 1: Generate band identity
with open("my-song.mp3", "rb") as f:
    resp = requests.post(
        f"{API}/api/band-generation",
        headers=HEADERS,
        files={"audio": ("my-song.mp3", f, "audio/mpeg")},
        data={
            "mode": "explore",
            "artistType": "ensemble",
            "userGenre": "synthwave"
        },
        timeout=120
    )

result = json.loads(resp.text.strip())
winner = result["winner"]
print(f"Band: {winner['bandName']} ({winner['genre']})")
print(f"Motto: {winner['band_motto']}")
print(f"Portraits: {len(result['portraitOptions'])} options")

# Phase 2: Render card with chosen portrait
card_resp = requests.post(
    f"{API}/api/band-generation/render-card",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "winner": winner,
        "soulData": result["soulData"],
        "selectedPortraitUrl": result["portraitOptions"][0],
        "portraitOptions": result["portraitOptions"],
        "cardTheme": "dark",
        "artStyle": "realistic",
        "songName": result["songName"],
        "featuredTrackUrl": result.get("featuredTrackUrl", ""),
        "creditsDeducted": result["creditsDeducted"]
    },
    timeout=60
)

card = card_resp.json()
print(f"Card: {card['cardImageUrl']}")
print(f"Saved as creation: {card['creationId']}")
```
