87 lines
3.0 KiB
JavaScript
87 lines
3.0 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const db = require('../db');
|
|
const { mixingRules, compatLabels } = require('../../shared/mixing-rules');
|
|
const camelotToMusicKey = require('../../shared/camelot-to-musickey');
|
|
const colorMatrix = require('../../shared/color-matrix');
|
|
|
|
// GET /api/suggestions?key=4B&bpm=120&artist=...&title=...
|
|
router.get('/', async (req, res) => {
|
|
const currentKey = req.query.key;
|
|
const currentBpm = parseInt(req.query.bpm, 10) || 120;
|
|
const artist = req.query.artist || '';
|
|
const title = req.query.title || '';
|
|
|
|
if (!currentKey || !mixingRules[currentKey]) {
|
|
return res.status(400).json({ error: 'Valid current key required (e.g. 4B, 1A)' });
|
|
}
|
|
|
|
try {
|
|
const compatibleKeys = mixingRules[currentKey]; // Array of 8 keys: [Perfect, Quinte -1, ...]
|
|
|
|
// BPM tolerance: ±6% by default for seamless mixing
|
|
let minBpm = Math.round(currentBpm * 0.94);
|
|
let maxBpm = Math.round(currentBpm * 1.06);
|
|
|
|
// 1. Fetch tracks from PostgreSQL within ±6%
|
|
let tracks = await db.getSuggestions(compatibleKeys, minBpm, maxBpm, 10, artist, title);
|
|
|
|
// 2. If fewer than 8 tracks, widen BPM range to ±12%
|
|
if (tracks.length < 8) {
|
|
minBpm = Math.round(currentBpm * 0.88);
|
|
maxBpm = Math.round(currentBpm * 1.12);
|
|
tracks = await db.getSuggestions(compatibleKeys, minBpm, maxBpm, 10, artist, title);
|
|
}
|
|
|
|
// 3. If fewer than 5 tracks, widen BPM range to ±20% for smaller libraries
|
|
if (tracks.length < 5) {
|
|
minBpm = Math.round(currentBpm * 0.80);
|
|
maxBpm = Math.round(currentBpm * 1.20);
|
|
tracks = await db.getSuggestions(compatibleKeys, minBpm, maxBpm, 10, artist, title);
|
|
}
|
|
|
|
// 4. If still empty, fetch any compatible key track (50-220 BPM)
|
|
if (tracks.length === 0) {
|
|
tracks = await db.getSuggestions(compatibleKeys, 50, 220, 10, artist, title);
|
|
}
|
|
|
|
// 3. Map tracks with compatibility info and colors
|
|
const enriched = tracks.map(t => {
|
|
const ruleIdx = compatibleKeys.indexOf(t.camelot_key);
|
|
const compatLabel = ruleIdx !== -1 ? compatLabels[ruleIdx] : 'Compatible';
|
|
const color = colorMatrix[t.camelot_key] || '#4F9D69';
|
|
const musicKey = t.music_key || camelotToMusicKey[t.camelot_key] || '';
|
|
|
|
return {
|
|
id: t.id,
|
|
title: t.title,
|
|
artist: t.artist,
|
|
key: t.camelot_key,
|
|
c: t.camelot_key,
|
|
bpm: t.bpm,
|
|
musicKey: musicKey,
|
|
m: musicKey,
|
|
color: color,
|
|
compat: ruleIdx !== -1 ? ruleIdx : 0,
|
|
compatLabel: compatLabel,
|
|
cover_url: t.cover_url || null,
|
|
tags: t.tags || []
|
|
};
|
|
});
|
|
|
|
// Sort by compatibility level (Perfect -> Quinte -> Energy -> ...)
|
|
enriched.sort((a, b) => a.compat - b.compat);
|
|
|
|
res.json({
|
|
status: 'success',
|
|
data: enriched
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error('[Suggestions] Error:', err.message);
|
|
res.status(500).json({ error: 'Failed to generate suggestions' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|