import type { CTPDocument } from "@/lib/ctp/schema"; import type { AnalysisInput, AnalysisProvider } from "@/lib/analysis/providers"; // Section templates keyed by duration bucket interface SectionTemplate { labels: string[]; weights: number[]; } function getTemplate(duration: number): SectionTemplate { if (duration < 120) { return { labels: ["Intro", "Verse / Chorus", "Outro"], weights: [0.12, 0.76, 0.12], }; } if (duration < 240) { return { labels: ["Intro", "Verse", "Chorus", "Verse + Chorus", "Outro"], weights: [0.08, 0.22, 0.20, 0.38, 0.12], }; } if (duration < 360) { return { labels: ["Intro", "Verse", "Chorus", "Verse + Chorus", "Bridge", "Outro"], weights: [0.07, 0.20, 0.18, 0.33, 0.10, 0.12], }; } return { labels: ["Intro", "Verse", "Chorus", "Verse + Chorus", "Instrumental", "Bridge", "Outro"], weights: [0.06, 0.18, 0.16, 0.30, 0.10, 0.10, 0.10], }; } function buildFallback(input: AnalysisInput): CTPDocument { return { version: "1.0", metadata: { title: input.title ?? "Unknown Title", artist: input.artist ?? "Unknown Artist", mbid: input.mbid ?? null, duration_seconds: input.duration, contributed_by: input.contributed_by, verified: false, created_at: new Date().toISOString(), }, count_in: { enabled: true, bars: 2, use_first_section_tempo: true, }, sections: [ { label: "Song", start_bar: 1, bpm: input.bpm, time_signature: { numerator: 4, denominator: 4 }, transition: "step", }, ], }; } export const algorithmicProvider: AnalysisProvider = { id: "algorithmic", label: "Algorithmic (no AI)", type: "algorithmic", async isAvailable() { return { available: true }; }, async generateCTP(input: AnalysisInput): Promise { try { const { bpm, duration, title } = input; const totalBars = Math.floor((duration * bpm) / 240); const template = getTemplate(duration); const { labels, weights } = template; // Allocate bars per section const rawBars = weights.map((w) => Math.round(totalBars * w)); // Adjust last section so total is exact const allocatedSum = rawBars.reduce((a, b) => a + b, 0); const diff = totalBars - allocatedSum; rawBars[rawBars.length - 1] = Math.max(1, rawBars[rawBars.length - 1] + diff); // Determine time signature const lowerTitle = title?.toLowerCase() ?? ""; const numerator = lowerTitle.includes("waltz") || lowerTitle.includes("3/4") ? 3 : 4; const timeSignature = { numerator, denominator: 4 as const }; // Build sections with cumulative start_bar let currentBar = 1; const sections = labels.map((label, i) => { const start_bar = currentBar; currentBar += rawBars[i]; return { label, start_bar, bpm, time_signature: timeSignature, transition: "step" as const, }; }); return { version: "1.0", metadata: { title: input.title ?? "Unknown Title", artist: input.artist ?? "Unknown Artist", mbid: input.mbid ?? null, duration_seconds: duration, contributed_by: input.contributed_by, verified: false, created_at: new Date().toISOString(), }, count_in: { enabled: true, bars: 2, use_first_section_tempo: true, }, sections, }; } catch { // Algorithmic provider must never surface an error return buildFallback(input); } }, };