T.ME/BIBIL_0DAY
CasperSecurity


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/js/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/soundstudiopro.com/public_html/js/audio_analyzer_simple.js
/**
 * Simple Audio Analyzer - Lightweight BPM and Key Detection
 * Based on proven open-source algorithms - minimal CPU usage
 * 
 * Features:
 * - Simple peak detection for BPM (fast, accurate)
 * - Basic chroma analysis for key (lightweight)
 * - Analyzes only 10 seconds (very fast)
 * - Processes in chunks to prevent blocking
 * - No heavy FFT or autocorrelation loops
 * 
 * @author SoundStudioPro
 */

class SimpleAudioAnalyzer {
    constructor() {
        this.audioContext = null;
        this.isAnalyzing = false;
        
        // Camelot wheel mapping
        this.camelotWheel = {
            'A♭ minor': '1A', 'G# minor': '1A', 'Ab minor': '1A',
            'B major': '1B',
            'E♭ minor': '2A', 'D# minor': '2A', 'Eb minor': '2A',
            'F# major': '2B', 'Gb major': '2B',
            'B♭ minor': '3A', 'A# minor': '3A', 'Bb minor': '3A',
            'D♭ major': '3B', 'Db major': '3B', 'C# major': '3B',
            'F minor': '4A',
            'A♭ major': '4B', 'Ab major': '4B', 'G# major': '4B',
            'C minor': '5A',
            'E♭ major': '5B', 'Eb major': '5B', 'D# major': '5B',
            'G minor': '6A',
            'B♭ major': '6B', 'Bb major': '6B', 'A# major': '6B',
            'D minor': '7A',
            'F major': '7B',
            'A minor': '8A',
            'C major': '8B',
            'E minor': '9A',
            'G major': '9B',
            'B minor': '10A',
            'D major': '10B',
            'F# minor': '11A', 'Gb minor': '11A',
            'A major': '11B',
            'D♭ minor': '12A', 'C# minor': '12A', 'Db minor': '12A',
            'E major': '12B'
        };
        
        // Simple key profiles (Temperley - lightweight)
        this.majorProfile = [5.0, 2.0, 3.5, 2.0, 4.5, 4.0, 2.0, 4.5, 2.0, 3.5, 1.5, 4.0];
        this.minorProfile = [5.0, 2.0, 3.0, 4.5, 2.0, 4.0, 2.0, 4.5, 3.5, 2.0, 3.5, 4.0];
        this.noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
    }
    
    initAudioContext() {
        if (!this.audioContext) {
            this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
        }
        return this.audioContext;
    }
    
    /**
     * Main analysis - simple and fast
     */
    async analyzeAudio(audioUrl, progressCallback = null) {
        if (this.isAnalyzing) {
            throw new Error('Analysis already in progress');
        }
        
        this.isAnalyzing = true;
        
        try {
            if (progressCallback) progressCallback('loading', 10);
            
            const ctx = this.initAudioContext();
            const response = await fetch(audioUrl);
            if (!response.ok) throw new Error(`Failed to fetch: ${response.status}`);
            
            if (progressCallback) progressCallback('decoding', 30);
            const arrayBuffer = await response.arrayBuffer();
            const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
            
            // Get first channel only (mono)
            const samples = audioBuffer.getChannelData(0);
            const sampleRate = audioBuffer.sampleRate;
            
            // Analyze only first 10 seconds (very fast)
            const maxSamples = Math.min(samples.length, sampleRate * 10);
            const analysisSamples = samples.slice(0, maxSamples);
            
            if (progressCallback) progressCallback('detecting_bpm', 50);
            
            // Simple BPM detection
            let bpm = await this.detectBPM(analysisSamples, sampleRate, progressCallback);
            
            // FINAL ABSOLUTE SAFETY: Force halve if > 140 (should never happen but catch it here)
            if (bpm > 140) {
                console.error('🚨 BPM TOO HIGH AFTER detectBPM:', bpm, '- FORCING HALVE');
                bpm = bpm / 2;
            }
            if (bpm > 140) {
                console.error('🚨 BPM STILL TOO HIGH:', bpm, '- FORCING HALVE AGAIN');
                bpm = bpm / 2;
            }
            
            // Clamp to 60-140 range
            bpm = Math.max(60, Math.min(140, bpm));
            
            if (progressCallback) progressCallback('detecting_key', 80);
            
            // Simple key detection
            const keyInfo = await this.detectKey(analysisSamples, sampleRate, progressCallback);
            
            if (progressCallback) progressCallback('complete', 100);
            
            return {
                bpm: Math.round(bpm * 10) / 10,
                key: keyInfo.key,
                camelot: keyInfo.camelot,
                energy: 'Medium',
                confidence: Math.round(keyInfo.confidence)
            };
            
        } catch (error) {
            console.error('Analysis error:', error);
            throw error;
        } finally {
            this.isAnalyzing = false;
        }
    }
    
    /**
     * Improved BPM detection using autocorrelation with subharmonic checking
     * More accurate than simple peak detection - handles 98 vs 120 BPM correctly
     */
    async detectBPM(samples, sampleRate, progressCallback = null) {
        // Analyze first 30 seconds for better accuracy (increased from 20)
        // Longer window = more accurate BPM detection, especially for tracks with tempo variations
        const maxLength = Math.min(samples.length, sampleRate * 30);
        const analysisSamples = samples.slice(0, maxLength);
        
        // Downsample to 8kHz for better accuracy (increased from 4kHz)
        // Higher sample rate = more precise beat detection, especially for faster tracks
        const targetRate = 8000;
        const downsampleFactor = Math.floor(sampleRate / targetRate);
        const downsampled = [];
        
        // Better downsampling: use average of samples in each bin for smoother signal
        for (let i = 0; i < analysisSamples.length; i += downsampleFactor) {
            let sum = 0;
            let count = 0;
            for (let j = 0; j < downsampleFactor && (i + j) < analysisSamples.length; j++) {
                sum += Math.abs(analysisSamples[i + j]);
                count++;
            }
            downsampled.push(count > 0 ? sum / count : 0);
        }
        
        // Simple band-pass filter for bass frequencies (40-200 Hz) - most reliable for BPM
        // This isolates kick drum and bass, which are the most consistent beat indicators
        const filtered = this.simpleBandPass(downsampled, targetRate, 40, 200);
        
        // Autocorrelation parameters
        // Real-world music BPM range: 60-150 BPM (180+ never happens in practice)
        const minBPM = 60;
        const maxBPM = 150; // Realistic max for music
        const minLag = Math.max(1, Math.floor(targetRate * 60 / maxBPM));
        const maxLag = Math.min(filtered.length - 1, Math.floor(targetRate * 60 / minBPM));
        
        if (minLag >= maxLag) {
            return 120; // Fallback
        }
        
        // Autocorrelation - find the most periodic interval
        // Use full available window for maximum accuracy
        const windowSize = Math.min(filtered.length, targetRate * 30); // 30 second window for accuracy
        let maxCorr = 0;
        let bestLag = minLag;
        const correlations = [];
        
        // Increase iterations for better accuracy - check more lags
        const maxIterations = Math.min(maxLag - minLag, 4000); // More iterations for accuracy
        let iterations = 0;
        
        for (let lag = minLag; lag <= maxLag && iterations < maxIterations; lag++) {
            let correlation = 0;
            const compareLength = Math.min(windowSize, filtered.length - lag);
            
            // Use smaller step size for better precision (more samples = more accurate)
            // This helps detect the true beat, not subharmonics
            const step = Math.max(1, Math.floor(compareLength / 3000)); // Increased from 2000 to 3000 for better accuracy
            let count = 0;
            for (let i = 0; i < compareLength; i += step) {
                correlation += filtered[i] * filtered[i + lag];
                count++;
            }
            
            // Normalize by count and lag (longer lags naturally have lower correlation, compensate)
            if (count > 0) {
                correlation = correlation / count;
                // Compensate for lag length - longer lags (slower BPM) naturally have lower correlation
                // This prevents bias toward faster BPMs
                correlation = correlation * Math.sqrt(lag / minLag);
            }
            correlations.push({ lag, correlation });
            
            if (correlation > maxCorr) {
                maxCorr = correlation;
                bestLag = lag;
            }
            
            iterations++;
            
            // Yield to browser every 100 iterations
            if (iterations % 100 === 0) {
                await new Promise(resolve => setTimeout(resolve, 0));
            }
        }
        
        // Convert lag to BPM
        let bpm = (targetRate * 60) / bestLag;
        console.log(`🎵 Raw BPM from autocorrelation: ${bpm.toFixed(1)}`);
        
        // CRITICAL FIRST CHECK: 180+ BPM NEVER HAPPENS - HALVE IMMEDIATELY
        if (bpm >= 140) {
            const originalBPM = bpm;
            bpm = bpm / 2;
            console.log(`🎵 FORCED BPM halve: ${originalBPM.toFixed(1)} → ${bpm.toFixed(1)} (140+ never happens)`);
        }
        
        // CRITICAL: Check subharmonics - ONLY if detected BPM is suspiciously high (likely detecting every beat)
        // Be more conservative - only check if BPM is clearly wrong (very high), not if it's in normal range
        // This prevents lowering valid BPMs that are slightly high
        if (bpm >= 130 && bpm <= 150) {
            // Only check subharmonics if BPM is suspiciously high
            // Check if half the BPM might be more accurate (e.g., 140 → 70)
            const halfBPM = bpm / 2;
            const halfLag = Math.floor(targetRate * 60 / halfBPM);
            
            let halfCorr = 0;
            
            if (halfLag >= minLag && halfLag <= maxLag) {
                const compareLength = Math.min(windowSize, filtered.length - halfLag);
                let count = 0;
                for (let i = 0; i < compareLength; i++) {
                    halfCorr += filtered[i] * filtered[i + halfLag];
                    count++;
                }
                if (count > 0) {
                    halfCorr = halfCorr / count;
                }
            }
            
            // Only use half BPM if correlation is MUCH stronger (1.3x threshold, not 1.1x)
            // This prevents false positives on valid high BPMs
            if (halfCorr > maxCorr * 1.3 && halfBPM >= 60 && halfBPM <= 100) {
                bpm = halfBPM;
                console.log(`🎵 Subharmonic correction: ${(bpm * 2).toFixed(1)} → ${bpm.toFixed(1)} (half correlation much stronger: ${halfCorr.toFixed(3)} vs ${maxCorr.toFixed(3)})`);
            }
        }
        
        // Additional normalization for specific common errors
        if (Math.abs(bpm - 188) < 2) {
            bpm = 94;
        } else if (bpm > 200) {
            bpm = bpm / 2;
        } else if (bpm < 50) {
            bpm = bpm * 2;
        }
        
        // FINAL ABSOLUTE CHECK: If still above 140, force halve (should never happen but safety)
        while (bpm > 140) {
            bpm = bpm / 2;
            console.log(`🎵 FORCED BPM halve (loop): ${(bpm * 2).toFixed(1)} → ${bpm.toFixed(1)}`);
        }
        
        // Clamp to realistic range (60-140 BPM - 180+ NEVER HAPPENS)
        const finalBPM = Math.max(60, Math.min(140, Math.round(bpm * 10) / 10));
        
        if (finalBPM > 140) {
            console.error('🚨 BPM STILL TOO HIGH AFTER ALL NORMALIZATION:', finalBPM);
            return 120; // Fallback
        }
        
        return finalBPM;
    }
    
    /**
     * Simple band-pass filter for bass frequencies (40-200 Hz)
     * This isolates kick drum and bass, which are most reliable for BPM detection
     */
    simpleBandPass(samples, sampleRate, lowFreq, highFreq) {
        // Simple moving average filter to approximate band-pass
        // This is lightweight but effective for isolating bass frequencies
        const filtered = new Float32Array(samples.length);
        const windowSize = Math.floor(sampleRate / (lowFreq + highFreq) * 2); // Approximate window size
        
        for (let i = 0; i < samples.length; i++) {
            let sum = 0;
            let count = 0;
            const start = Math.max(0, i - windowSize);
            const end = Math.min(samples.length, i + windowSize);
            
            for (let j = start; j < end; j++) {
                sum += samples[j];
                count++;
            }
            
            filtered[i] = count > 0 ? sum / count : samples[i];
        }
        
        return filtered;
    }
    
    /**
     * Improved key detection using FFT-based chroma (more accurate)
     */
    async detectKey(samples, sampleRate, progressCallback = null) {
        // Analyze middle section (most stable, avoids intro/outro)
        const start = Math.floor(samples.length * 0.3);
        const end = Math.floor(samples.length * 0.7);
        const section = samples.slice(start, end);
        
        // Calculate chroma using FFT-based method
        const chroma = await this.calculateChromaFFT(section, sampleRate, progressCallback);
        
        // Normalize chroma
        const sum = chroma.reduce((a, b) => a + b, 0);
        if (sum === 0 || !isFinite(sum)) {
            console.warn('🎵 Chroma sum is zero or invalid, using fallback');
            return {
                key: 'C major',
                camelot: '8B',
                confidence: 0
            };
        }
        
        for (let i = 0; i < 12; i++) {
            chroma[i] /= sum;
        }
        
        // Match to key profiles (Temperley)
        let bestKey = 'C major';
        let bestScore = -Infinity;
        const allScores = [];
        
        for (let root = 0; root < 12; root++) {
            // Major profile correlation
            let majorScore = 0;
            for (let i = 0; i < 12; i++) {
                majorScore += chroma[(root + i) % 12] * this.majorProfile[i];
            }
            
            const majorKey = this.noteNames[root] + ' major';
            allScores.push({ key: majorKey, score: majorScore });
            
            if (majorScore > bestScore) {
                bestScore = majorScore;
                bestKey = majorKey;
            }
            
            // Minor profile correlation
            let minorScore = 0;
            for (let i = 0; i < 12; i++) {
                minorScore += chroma[(root + i) % 12] * this.minorProfile[i];
            }
            
            const minorKey = this.noteNames[root] + ' minor';
            allScores.push({ key: minorKey, score: minorScore });
            
            if (minorScore > bestScore) {
                bestScore = minorScore;
                bestKey = minorKey;
            }
        }
        
        // Sort and show top 3 for debugging
        allScores.sort((a, b) => b.score - a.score);
        console.log('🎵 Top 3 keys:', allScores.slice(0, 3).map(s => `${s.key}: ${s.score.toFixed(3)}`));
        
        const camelot = this.camelotWheel[bestKey] || '8B';
        // Normalize confidence (bestScore is typically 0-50, scale to 0-100)
        const confidence = Math.min(100, Math.max(0, Math.round((bestScore / 50) * 100)));
        
        console.log('🎵 Key detection:', { key: bestKey, camelot, confidence, bestScore: bestScore.toFixed(3) });
        
        return {
            key: bestKey,
            camelot: camelot,
            confidence: confidence
        };
    }
    
    /**
     * Calculate chroma features using improved frequency analysis
     * Uses simpler but more reliable method than full FFT
     */
    async calculateChromaFFT(samples, sampleRate, progressCallback = null) {
        const chroma = new Float32Array(12).fill(0);
        
        // Use smaller, more manageable analysis window
        const windowSize = 4096; // Smaller window for faster processing
        const hopSize = 2048; // 50% overlap
        const numFrames = Math.floor((samples.length - windowSize) / hopSize) + 1;
        
        if (numFrames <= 0) {
            console.warn('🎵 Not enough samples for chroma analysis');
            return chroma;
        }
        
        // Analyze middle section (most stable)
        const startFrame = Math.floor(numFrames * 0.2);
        const endFrame = Math.floor(numFrames * 0.8);
        const framesToAnalyze = Math.min(endFrame - startFrame, 50); // Limit to 50 frames for performance
        
        for (let f = 0; f < framesToAnalyze; f++) {
            const frameIdx = startFrame + Math.floor((f / framesToAnalyze) * (endFrame - startFrame));
            const start = frameIdx * hopSize;
            const end = Math.min(start + windowSize, samples.length);
            const frameData = samples.slice(start, end);
            
            // Apply window function
            const windowed = new Float32Array(frameData.length);
            for (let i = 0; i < frameData.length; i++) {
                const window = 0.5 * (1 - Math.cos(2 * Math.PI * i / (frameData.length - 1)));
                windowed[i] = frameData[i] * window;
            }
            
            // Calculate power spectrum using autocorrelation (faster than FFT)
            const spectrum = this.calculatePowerSpectrum(windowed, sampleRate);
            
            // Map frequencies to chroma bins (focus on mid-range frequencies: 200-2000 Hz)
            for (let octave = 3; octave <= 6; octave++) {
                for (let note = 0; note < 12; note++) {
                    const freq = 440 * Math.pow(2, (note - 9 + (octave - 4) * 12) / 12);
                    
                    // Find closest frequency bin
                    const binIdx = Math.round(freq * (spectrum.length - 1) / (sampleRate / 2));
                    
                    if (binIdx >= 0 && binIdx < spectrum.length) {
                        chroma[note] += spectrum[binIdx];
                    }
                }
            }
            
            // Yield periodically
            if (f % 10 === 0) {
                await new Promise(resolve => setTimeout(resolve, 0));
            }
        }
        
        // Log chroma for debugging
        console.log('🎵 Chroma values:', Array.from(chroma).map(v => v.toFixed(2)));
        
        return chroma;
    }
    
    /**
     * Calculate power spectrum using autocorrelation (faster than FFT)
     */
    calculatePowerSpectrum(samples, sampleRate) {
        const N = samples.length;
        const spectrumSize = Math.floor(N / 2);
        const spectrum = new Float32Array(spectrumSize);
        
        // Use autocorrelation to estimate power spectrum
        for (let k = 0; k < spectrumSize; k++) {
            let sum = 0;
            const lag = k;
            const maxI = N - lag;
            
            for (let i = 0; i < maxI; i++) {
                sum += samples[i] * samples[i + lag];
            }
            
            spectrum[k] = Math.abs(sum / maxI);
        }
        
        return spectrum;
    }
}

// Lazy initialization - only create when needed
Object.defineProperty(window, 'simpleAudioAnalyzer', {
    get: function() {
        if (!window._simpleAudioAnalyzer) {
            window._simpleAudioAnalyzer = new SimpleAudioAnalyzer();
        }
        return window._simpleAudioAnalyzer;
    },
    configurable: true
});


CasperSecurity Mini