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/.cursor-server/data/User/History/-3ca396d7/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/.cursor-server/data/User/History/-3ca396d7/52XT.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
            const bpm = await this.detectBPM(analysisSamples, sampleRate, progressCallback);
            
            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 20 seconds for better accuracy (was 10)
        const maxLength = Math.min(samples.length, sampleRate * 20);
        const analysisSamples = samples.slice(0, maxLength);
        
        // Downsample to 4kHz for speed
        const targetRate = 4000;
        const downsampleFactor = Math.floor(sampleRate / targetRate);
        const downsampled = [];
        
        for (let i = 0; i < analysisSamples.length; i += downsampleFactor) {
            downsampled.push(Math.abs(analysisSamples[i]));
        }
        
        // 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; // Lowered from 180 - 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
        const windowSize = Math.min(filtered.length, targetRate * 10); // 10 second window
        let maxCorr = 0;
        let bestLag = minLag;
        const correlations = [];
        
        // Limit iterations for performance
        const maxIterations = Math.min(maxLag - minLag, 2000);
        let iterations = 0;
        
        for (let lag = minLag; lag <= maxLag && iterations < maxIterations; lag++) {
            let correlation = 0;
            const compareLength = Math.min(windowSize, filtered.length - lag);
            
            for (let i = 0; i < compareLength; i++) {
                correlation += filtered[i] * filtered[i + lag];
            }
            
            // Normalize by length
            correlation = correlation / compareLength;
            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;
        
        // CRITICAL: Check subharmonics - if detected BPM is close to 120, check if 98-100 is more likely
        // This handles cases where the algorithm detects every beat instead of main beats
        if (bpm >= 115 && bpm <= 125) {
            // Check if half the BPM (around 60) or 0.82x (around 98-100) might be more accurate
            const halfBPM = bpm / 2;
            const altBPM = bpm * 0.82; // 120 * 0.82 ≈ 98
            
            // Check correlation at these alternative lags
            const halfLag = Math.floor(targetRate * 60 / halfBPM);
            const altLag = Math.floor(targetRate * 60 / altBPM);
            
            let halfCorr = 0;
            let altCorr = 0;
            
            if (halfLag >= minLag && halfLag <= maxLag) {
                const compareLength = Math.min(windowSize, filtered.length - halfLag);
                for (let i = 0; i < compareLength; i++) {
                    halfCorr += filtered[i] * filtered[i + halfLag];
                }
                halfCorr = halfCorr / compareLength;
            }
            
            if (altLag >= minLag && altLag <= maxLag) {
                const compareLength = Math.min(windowSize, filtered.length - altLag);
                for (let i = 0; i < compareLength; i++) {
                    altCorr += filtered[i] * filtered[i + altLag];
                }
                altCorr = altCorr / compareLength;
            }
            
            // If alternative correlations are significantly stronger, use them
            if (altCorr > maxCorr * 1.1 && altBPM >= 90 && altBPM <= 110) {
                bpm = altBPM;
                console.log(`🎵 Subharmonic correction: 120 → ${bpm.toFixed(1)} (alt correlation stronger)`);
            } else if (halfCorr > maxCorr * 1.1 && halfBPM >= 60 && halfBPM <= 100) {
                bpm = halfBPM;
                console.log(`🎵 Subharmonic correction: 120 → ${bpm.toFixed(1)} (half correlation stronger)`);
            }
        }
        
        // Normalize common errors - aggressively round down high BPMs
        // Autocorrelation often detects harmonics (double the actual BPM)
        if (bpm > 140) {
            // For very high BPMs, check if half would be more reasonable
            const halfBPM = bpm / 2;
            if (halfBPM >= 60 && halfBPM <= 140) {
                // Check correlation at half BPM to confirm
                const halfLag = Math.floor(targetRate * 60 / halfBPM);
                if (halfLag >= minLag && halfLag <= maxLag) {
                    let halfCorr = 0;
                    const compareLength = Math.min(windowSize, filtered.length - halfLag);
                    for (let i = 0; i < compareLength; i++) {
                        halfCorr += filtered[i] * filtered[i + halfLag];
                    }
                    halfCorr = halfCorr / compareLength;
                    
                    // If half BPM has strong correlation (at least 80% of max), use it
                    if (halfCorr > maxCorr * 0.8) {
                        bpm = halfBPM;
                        console.log(`🎵 High BPM correction: ${(bpm * 2).toFixed(1)} → ${bpm.toFixed(1)} (halved)`);
                    }
                }
            }
        }
        
        // 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 aggressive rounding down: if still above 140, just halve it
        // Most real-world music is 60-140 BPM, anything above is likely a harmonic
        if (bpm > 140) {
            bpm = bpm / 2;
            console.log(`🎵 Final high BPM correction: ${(bpm * 2).toFixed(1)} → ${bpm.toFixed(1)}`);
        }
        
        // Clamp to reasonable range (60-140 BPM for most music)
        return Math.max(60, Math.min(140, Math.round(bpm * 10) / 10));
    }
    
    /**
     * 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;
        
        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];
            }
            
            if (majorScore > bestScore) {
                bestScore = majorScore;
                bestKey = this.noteNames[root] + ' major';
            }
            
            // Minor profile correlation
            let minorScore = 0;
            for (let i = 0; i < 12; i++) {
                minorScore += chroma[(root + i) % 12] * this.minorProfile[i];
            }
            
            if (minorScore > bestScore) {
                bestScore = minorScore;
                bestKey = this.noteNames[root] + ' minor';
            }
        }
        
        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 FFT (more accurate than simple correlation)
     */
    async calculateChromaFFT(samples, sampleRate, progressCallback = null) {
        const chroma = new Float32Array(12).fill(0);
        const fftSize = 2048; // FFT size for frequency analysis
        const hopSize = 512; // Hop size between windows
        
        // Analyze multiple octaves (3-6) for better accuracy
        const numFrames = Math.floor((samples.length - fftSize) / hopSize);
        
        if (numFrames <= 0) {
            console.warn('🎵 Not enough samples for FFT analysis');
            return chroma;
        }
        
        for (let frame = 0; frame < numFrames; frame++) {
            const start = frame * hopSize;
            const end = Math.min(start + fftSize, samples.length);
            const frameData = samples.slice(start, end);
            
            // Apply window function (Hann window) to reduce spectral leakage
            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;
            }
            
            // Simple FFT (using DFT for small size)
            const fft = this.simpleFFT(windowed);
            
            // Map frequencies to chroma bins
            for (let octave = 3; octave <= 6; octave++) {
                for (let note = 0; note < 12; note++) {
                    // Calculate frequency for this note in this octave
                    const freq = 440 * Math.pow(2, (note - 9 + (octave - 4) * 12) / 12);
                    const bin = Math.round(freq * fftSize / sampleRate);
                    
                    if (bin >= 0 && bin < fft.length) {
                        chroma[note] += fft[bin];
                    }
                }
            }
            
            // Yield to browser periodically
            if (frame % 50 === 0) {
                await new Promise(resolve => setTimeout(resolve, 0));
            }
        }
        
        return chroma;
    }
    
    /**
     * Simple FFT using Discrete Fourier Transform (for small sizes)
     */
    simpleFFT(samples) {
        const N = samples.length;
        const fft = new Float32Array(N);
        
        for (let k = 0; k < N; k++) {
            let real = 0;
            let imag = 0;
            
            for (let n = 0; n < N; n++) {
                const angle = -2 * Math.PI * k * n / N;
                real += samples[n] * Math.cos(angle);
                imag += samples[n] * Math.sin(angle);
            }
            
            fft[k] = Math.sqrt(real * real + imag * imag);
        }
        
        return fft;
    }
}

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