1) Data Model (PostgreSQL / Supabase)

Tables

sql
-- USERS create table app_user ( id uuid primary key default gen_random_uuid(), email text unique, created_at timestamptz not null default now() ); -- USER PROFILES / PREFERENCES (per state) create table user_pref ( id uuid primary key default gen_random_uuid(), user_id uuid not null references app_user(id) on delete cascade, state text not null check (state in ('focus','calm','sleep','high')), preferred_params jsonb not null default '{}'::jsonb, -- { diffHz, mixDb, mode, carrierHz, fadeSeconds } updated_at timestamptz not null default now(), unique (user_id, state) ); -- SESSIONS (one listening session) create table session ( id uuid primary key default gen_random_uuid(), user_id uuid not null references app_user(id) on delete cascade, state text not null check (state in ('focus','calm','sleep','high')), params jsonb not null, -- parameters used for this render (frozen) track_url text, -- CDN link to rendered audio generator text not null, -- 'elevenlabs' | 'stableaudio' | 'musicgen' started_at timestamptz not null default now(), ended_at timestamptz ); create index session_user_time_idx on session (user_id, started_at desc); -- FEEDBACK (explicit + implicit biosignals) create table feedback ( id uuid primary key default gen_random_uuid(), session_id uuid not null references session(id) on delete cascade, rating int check (rating between 1 and 5), comment text, metrics_before jsonb, -- {hr, hrv, rhr, eda, alpha, beta, theta, drowsiness, ...} metrics_after jsonb, computed_score numeric, -- normalised reward 0..1 (or z-score) created_at timestamptz not null default now() ); create index feedback_session_idx on feedback (session_id); -- OPTIMIZER STATE (per user+state) create table optimizer_state ( id uuid primary key default gen_random_uuid(), user_id uuid not null references app_user(id) on delete cascade, state text not null check (state in ('focus','calm','sleep','high')), algo text not null default 'rule-v1', -- 'rule-v1' | 'gp-v1' | ... history jsonb not null default '[]'::jsonb, -- [{params:{...}, score:0.73, ts:"..."}, ...] last_suggested jsonb, -- params suggested most recently updated_at timestamptz not null default now(), unique (user_id, state) );

Suggested indices

sql
create index session_state_idx on session (state, started_at desc); create index feedback_score_idx on feedback (computed_score desc);

2) Parameter Object (shared contract)

json
{ "mode": "binaural", // 'binaural' | 'isochronic' "diffHz": 10.0, // 4-20 depending on target state "mixDb": -18.0, // -24 .. -12 "carrierHz": 200.0, // 150 .. 500 (isochronic carrier) "fadeSeconds": 6, "durationSec": 120, "envelope": [ { "t": 0, "diffHz": 9.5, "mixDb": -20 }, { "t": 60, "diffHz": 10.0, "mixDb": -18 } ] }

3) Backend API (Node/Express)

3.1 Create session → generate & render

POST /api/session
Request
json
{ "userId": "UUID", "state": "focus", "durationSec": 120, "seed": "optional", "styleHints": ["ambient","minimal"] }
Server flow
  1. Load user_pref for userId+state (fallback to default preset table).
  1. Call ElevenLabs Music API → get baseTrackUrl.
  1. POST to DSP worker /render with { baseUrl, params }.
  1. Upload resulting audio to S3 → trackUrl.
  1. Insert session with frozen params & trackUrl.
  1. Return payload.
Response
json
{ "sessionId": "UUID", "trackUrl": "https://cdn.example.com/audio/..", "parameters": { "mode":"binaural","diffHz":10,"mixDb":-18,"fadeSeconds":6 }, "generator": "elevenlabs" }

3.2 Submit feedback

POST /api/feedback
Request
json
{ "sessionId": "UUID", "rating": 4, "comment": "Good focus after ~3 min", "metricsBefore": { "hrv": 58, "hr": 72 }, "metricsAfter": { "hrv": 66, "hr": 67 } }
Server flow
  1. Compute computed_score (see §5).
  1. Insert feedback.
  1. Call Optimizer /update with { userId, state, paramsUsed, score }.
  1. Update optimizer_state + optionally user_pref.preferred_params.
  1. Return acknowledgement + next suggestion.
Response
json
{ "ok": true, "nextSuggestedParams": { "mode":"binaural","diffHz":11,"mixDb":-18 } }

3.3 Get next suggestion (stateless)

POST /api/optimizer/suggest
json
{ "userId": "UUID", "state": "focus", "context": { "timeOfDay": "afternoon", "priorScore": 0.71 } }
Response
json
{ "params": { "mode":"binaural","diffHz":10.5,"mixDb":-17 }, "rationale": "Stable high HRV response last 2 sessions; small exploration step." }

4) DSP Worker API (Python FastAPI)

/render (render entrainment + music)

Request
json
{ "baseUrl": "https://storage/ai-music.wav", "params": { "mode":"binaural","diffHz":10,"mixDb":-18,"carrierHz":200,"fadeSeconds":6,"durationSec":120 } }
Response (prod)
json
{ "trackUrl": "https://cdn.example.com/audio/processed/..wav" }
(PoC can return a data URI for simplicity.)

5) Optimizer Service API (Python FastAPI)

You can start with Rule-Based v1 and later switch to GP/BO v2 without changing the public contract.

5.1 Update after feedback

POST /opt/update
Request
json
{ "userId": "UUID", "state": "focus", "paramsUsed": { "mode":"binaural","diffHz":10,"mixDb":-18 }, "feedback": { "rating": 4, "metricsBefore": {"hrv":58}, "metricsAfter": {"hrv":66} } }
Server logic
  • Compute reward score (normalised 0..1). Suggested simple MVP function:
plain text
score = w1 * rating_norm + w2 * hrv_delta_norm + w3 * adherence where: rating_norm = rating/5 hrv_delta_norm = clamp((HRV_after - HRV_before)/20, 0, 1) adherence = 1 if session >= 2 min else 0.5 w1=0.5, w2=0.4, w3=0.1 (tune per state)
  • Append {paramsUsed, score, ts} to optimizer_state.history.
  • Produce nextSuggestedParams using rule set:
Rule-Based v1 examples
  • If score >= 0.75: keep diffHz, maybe ↓ mixDb by 1 dB to reduce fatigue.
  • If “too weak” (rating ≤ 3 and HRV↑ < small): ↑ mixDb by 2 dB (cap −14 dB).
  • If “too sleepy for focus”: ↑ diffHz by 1–2 Hz (towards 12–15 Hz).
  • If “anxious for calm”: ↓ diffHz by 1–2 Hz (towards 5–7 Hz).
  • Alternate mode after 3 low-scores to test isochronic vs binaural.
Response
json
{ "ok": true, "score": 0.78, "nextSuggestedParams": { "mode":"binaural","diffHz":10.5,"mixDb":-17 } }

5.2 Suggest without update

POST /opt/suggest
json
{ "userId":"UUID", "state":"focus", "context": { "timeOfDay":"morning" } }
Response
json
{ "params": { "mode":"binaural","diffHz":10,"mixDb":-18 }, "strategy":"rule-v1" }

6) Default Presets (seed data)

json
{ "focus": { "mode":"binaural","diffHz":10,"mixDb":-18,"fadeSeconds":6,"carrierHz":200,"durationSec":120 }, "calm": { "mode":"binaural","diffHz":7, "mixDb":-20,"fadeSeconds":8,"carrierHz":180,"durationSec":180 }, "sleep": { "mode":"isochronic","diffHz":4, "mixDb":-22,"fadeSeconds":10,"carrierHz":160,"durationSec":300 }, "high": { "mode":"isochronic","diffHz":16,"mixDb":-18,"fadeSeconds":4,"carrierHz":220,"durationSec":120 } }

7) Prisma (optional ORM mapping)

plain text
model AppUser { id String @id @default(uuid()) email String? @unique createdAt DateTime @default(now()) sessions Session[] prefs UserPref[] } model UserPref { id String @id @default(uuid()) userId String state String preferredParams Json updatedAt DateTime @default(now()) AppUser AppUser @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, state]) } model Session { id String @id @default(uuid()) userId String state String params Json trackUrl String? generator String startedAt DateTime @default(now()) endedAt DateTime? AppUser AppUser @relation(fields: [userId], references: [id], onDelete: Cascade) feedback Feedback[] @@index([userId, startedAt]) } model Feedback { id String @id @default(uuid()) sessionId String rating Int? comment String? metricsBefore Json? metricsAfter Json? computedScore Decimal? createdAt DateTime @default(now()) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) }

8) Frontend Integration (React snippets)

Start session
typescript
const startSession = async (state: 'focus'|'calm'|'sleep'|'high') => { const r = await fetch('/api/session', { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({ userId, state, durationSec: 120 }) }) const { sessionId, trackUrl, parameters } = await r.json() setSession({ sessionId, trackUrl, parameters }) audio.src = trackUrl audio.play() }
Submit feedback
typescript
await fetch('/api/feedback', { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({ sessionId, rating, comment, metricsBefore: { hrv: preHRV }, metricsAfter: { hrv: postHRV } }) }).then(r=>r.json()).then(({ nextSuggestedParams }) => cacheNext(nextSuggestedParams))

9) Privacy & Safety (MVP guardrails)

  • Store only aggregated biometrics needed for scoring (e.g., HRV, HR).
  • Add consent flag and link to privacy policy in UI.
  • Enforce min/max volume and session length caps; fade in/out.
  • Clear disclaimer: wellbeing only, not medical therapy.

10) Rollout Plan for the Optimiser

  • Week 1–2: Use presets only; log feedback/metrics.
  • Week 3–4: Enable rule-v1 adjustments after each feedback.
  • Week 5–6: Introduce gp-v1 (Bayesian) for opt-in power users; keep rule-v1 as fallback.