![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/public_html/api/ |
<?php
/**
* API Endpoint: Save Audio Analysis Results
* Stores BPM, key (with Camelot notation), and energy data from client-side analysis
*/
require_once '../config/database.php';
session_start();
header('Content-Type: application/json');
// Only accept POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
exit;
}
// Get JSON input
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
echo json_encode(['success' => false, 'error' => 'Invalid JSON input']);
exit;
}
$track_id = intval($input['track_id'] ?? 0);
$bpm = floatval($input['bpm'] ?? 0); // Allow decimal BPM values (e.g., 104.2)
$key = $input['key'] ?? null;
$camelot = $input['camelot'] ?? null;
$energy = $input['energy'] ?? null;
$confidence = intval($input['confidence'] ?? 0);
// Validate required fields
if (!$track_id || $bpm <= 0 || !$key) {
echo json_encode(['success' => false, 'error' => 'Missing required fields']);
exit;
}
// CRITICAL: Real-world music BPM is 60-140 max. Anything above 140 is definitely a harmonic (double).
// Normalize to match client-side logic
$originalBpm = $bpm;
// Safety: Check for invalid values (NaN, Infinity) that could cause infinite loops
if (!is_finite($bpm) || is_nan($bpm)) {
echo json_encode(['success' => false, 'error' => 'Invalid BPM value (NaN or Infinity)']);
exit;
}
// CRITICAL: Values > 140 NEVER happen in real music - halve immediately
if ($bpm > 140) {
$bpm = $bpm / 2;
error_log("šµ SERVER: Forced BPM halve $originalBpm ā $bpm (140+ never happens) for track $track_id");
// If still > 140 after halving, halve again (safety)
if ($bpm > 140) {
$bpm = $bpm / 2;
error_log("šµ SERVER: Second BPM halve ā $bpm for track $track_id");
}
}
// 188 is a common error (94*2) - fix it specifically
elseif (abs($bpm - 188) < 2) {
$bpm = 94;
error_log("šµ SERVER: Fixed common error 188 ā 94 for track $track_id");
}
// Values < 50: Very low, likely wrong, double them
elseif ($bpm < 50 && $bpm > 0) {
$doubled = $bpm * 2;
if ($doubled <= 140) {
$bpm = $doubled;
error_log("šµ SERVER: Normalized very low BPM $originalBpm ā $bpm for track $track_id");
}
}
// Validate BPM range (60-140 is realistic for music)
if ($bpm < 40 || $bpm > 140) {
// If somehow still out of range, clamp it
if ($bpm > 140) {
$bpm = 140;
error_log("šµ SERVER: Clamped BPM to 140 for track $track_id");
} elseif ($bpm < 40) {
$bpm = 60;
error_log("šµ SERVER: Clamped BPM to 60 for track $track_id");
}
}
// Round to 1 decimal place for consistency
$bpm = round($bpm, 1);
try {
$pdo = getDBConnection();
// Get current track metadata
$stmt = $pdo->prepare("SELECT metadata, user_id FROM music_tracks WHERE id = ?");
$stmt->execute([$track_id]);
$track = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$track) {
echo json_encode(['success' => false, 'error' => 'Track not found']);
exit;
}
// Parse existing metadata
$metadata = json_decode($track['metadata'] ?? '{}', true) ?: [];
// Check if already analyzed with good confidence (don't overwrite with lower quality analysis)
// BUT: Always allow user corrections (confidence = 100) to overwrite
$existingConfidence = $metadata['analysis_confidence'] ?? 0;
$isUserCorrection = ($confidence >= 100);
$hasExistingAnalysis = isset($metadata['bpm']) && isset($metadata['analysis_source']);
if (!$isUserCorrection && $hasExistingAnalysis && $existingConfidence >= $confidence) {
echo json_encode([
'success' => true,
'message' => 'Existing analysis has equal or higher confidence, keeping it',
'existing' => true
]);
exit;
}
// SINGLE SOURCE OF TRUTH: Store analyzed values in main fields
// This is the ONLY place we store BPM/key - no duplicate fields
$oldBpm = $metadata['bpm'] ?? 'none';
$oldKey = $metadata['key'] ?? 'none';
// Force overwrite - these are the authoritative values
$metadata['bpm'] = $bpm;
$metadata['key'] = $key;
$metadata['numerical_key'] = $camelot; // Camelot notation for DJ compatibility
$metadata['energy'] = $energy;
// Store analysis metadata (when/how it was analyzed)
$metadata['analysis_confidence'] = $confidence;
$metadata['analysis_date'] = date('Y-m-d H:i:s');
$metadata['analysis_source'] = 'client_web_audio';
// Remove old duplicate fields if they exist (cleanup)
unset($metadata['analyzed_bpm'], $metadata['analyzed_key'], $metadata['analyzed_camelot'], $metadata['analyzed_energy']);
// Encode metadata to JSON
$metadataJson = json_encode($metadata, JSON_UNESCAPED_UNICODE);
// Update database - store in metadata JSON
$stmt = $pdo->prepare("UPDATE music_tracks SET metadata = ?, updated_at = NOW() WHERE id = ?");
$result = $stmt->execute([$metadataJson, $track_id]);
if ($result) {
// Verify the update by reading it back
$verifyStmt = $pdo->prepare("SELECT metadata FROM music_tracks WHERE id = ?");
$verifyStmt->execute([$track_id]);
$verifyTrack = $verifyStmt->fetch(PDO::FETCH_ASSOC);
$verifyMetadata = json_decode($verifyTrack['metadata'] ?? '{}', true) ?: [];
$savedBpm = $verifyMetadata['bpm'] ?? null;
$savedKey = $verifyMetadata['key'] ?? null;
echo json_encode([
'success' => true,
'message' => 'Analysis saved successfully',
'data' => [
'bpm' => $bpm,
'key' => $key,
'camelot' => $camelot,
'energy' => $energy,
'confidence' => $confidence
],
'verified' => [
'bpm' => $savedBpm,
'key' => $savedKey
]
]);
// Log the analysis with before/after
error_log("šµ Audio analysis saved for track $track_id: BPM=$oldBpmā$bpm (verified: $savedBpm), Key=$oldKeyā$key (verified: $savedKey), Camelot=$camelot, Energy=$energy, Confidence=$confidence%");
if ($savedBpm != $bpm || $savedKey != $key) {
error_log("ā ļø WARNING: Saved values don't match! Expected BPM=$bpm, got $savedBpm. Expected Key=$key, got $savedKey");
}
} else {
$errorInfo = $stmt->errorInfo();
error_log("ā Database update failed for track $track_id: " . json_encode($errorInfo));
echo json_encode(['success' => false, 'error' => 'Database update failed', 'details' => $errorInfo]);
}
} catch (Exception $e) {
error_log("Audio analysis save error: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => 'Server error']);
}