lofi-remix-api / server.js
imran056's picture
Update server.js
d39d19a verified
// ═══════════════════════════════════════════════════════════
// COMPLETE UPDATED BACKEND SERVER
// Version 15.0 - Added "Slowed" Effect + Improved Bass Boost
// ═══════════════════════════════════════════════════════════
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const app = express();
const PORT = process.env.PORT || 7860;
app.use(cors());
app.use(express.json({ limit: '10mb' }));
const uploadsDir = path.join(__dirname, 'uploads');
const outputsDir = path.join(__dirname, 'outputs');
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true });
if (!fs.existsSync(outputsDir)) fs.mkdirSync(outputsDir, { recursive: true });
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, uploadsDir),
filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`)
});
const upload = multer({
storage: storage,
limits: { fileSize: 50 * 1024 * 1024 }
});
const mashupStorage = new Map();
const INSTRUMENTS = {
sad_piano: {
name: "Sad Piano",
mood: "sad",
command: "aevalsrc='0.04*sin(2*PI*(220+3*sin(0.05*t))*t)*exp(-0.12*mod(t,4.5))':d=12:s=44100,aecho=0.6:0.8:500:0.3,lowpass=f=3500"
},
melancholic_violin: {
name: "Melancholic Violin",
mood: "sad",
command: "aevalsrc='0.04*sin(2*PI*330*t)*sin(2*PI*0.25*t)*exp(-0.08*mod(t,5.5))':d=12:s=44100,vibrato=f=0.12:d=0.4,aecho=0.5:0.75:450:0.25"
},
romantic_piano: {
name: "Romantic Piano",
mood: "romantic",
command: "aevalsrc='0.04*(sin(2*PI*330*t)+sin(2*PI*415*t))*exp(-0.1*mod(t,4))':d=12:s=44100,aecho=0.5:0.7:400:0.3,chorus=0.25:0.5:30:0.2:0.12:2"
},
bittersweet_piano: {
name: "Bittersweet Piano",
mood: "sad_romantic",
command: "aevalsrc='0.04*(sin(2*PI*247*t)+sin(2*PI*330*t))*exp(-0.12*mod(t,4.5))*(1+0.2*sin(0.4*t))':d=12:s=44100,aecho=0.6:0.8:450:0.3,vibrato=f=0.1:d=0.35"
},
dreamy_pad: {
name: "Dreamy Pad",
mood: "ambient",
command: "aevalsrc='0.04*(sin(2*PI*165*t)+sin(2*PI*220*t)+sin(2*PI*330*t))':d=12:s=44100,aphaser=in_gain=0.3:out_gain=0.6:delay=2.5:decay=0.35:speed=0.2,aecho=0.4:0.6:350:0.25"
},
lofi_guitar: {
name: "LoFi Guitar",
mood: "chill",
command: "aevalsrc='0.04*sin(2*PI*196*t)*exp(-0.25*mod(t,3.5))*(1+0.1*random(0))':d=12:s=44100,chorus=0.4:0.6:35:0.22:0.13:2,lowpass=f=4200"
},
bright_synth: {
name: "Bright Synth",
mood: "upbeat",
command: "aevalsrc='0.04*(sin(2*PI*440*t)+sin(2*PI*554*t)+sin(2*PI*659*t))':d=12:s=44100,chorus=0.5:0.7:45:0.32:0.23:2,highpass=f=220"
}
};
// βœ… UPDATED EFFECTS - Added "slowed" + Improved "bass_boosted"
const EFFECTS = {
lofi: {
name: "LoFi Hip Hop",
description: "Vintage warmth with jazzy chill vibes (No Instruments)",
baseCommand: `asetrate=44100*0.90,aresample=44100,bass=g=5:f=85:width_type=h:width=100,treble=g=-3:f=8000,equalizer=f=200:t=q:width=1.5:g=-2,equalizer=f=1000:t=q:width=2:g=2,acompressor=threshold=-20dB:ratio=3:attack=10:release=120:makeup=3,vibrato=f=0.2:d=0.4,highpass=f=70,lowpass=f=4500,volume=1.3`,
instrumentVolume: 0,
instrumentMoods: ["chill", "sad_romantic", "ambient"]
},
// βœ… NEW: Simple Slowed Effect (just makes song slower, no reverb)
slowed: {
name: "Slowed",
description: "Just slowed down - pure slow tempo (No instruments, no reverb)",
baseCommand: `atempo=0.80,asetrate=44100*0.90,aresample=44100,equalizer=f=800:t=q:width=2:g=1.5,highpass=f=35,lowpass=f=14000,volume=1.15`,
instrumentVolume: 0,
instrumentMoods: []
},
slowed_reverb: {
name: "Slowed + Reverb",
description: "Simple & authentic - like YouTube/TikTok (clean vocals, no instruments)",
baseCommand: `atempo=0.85,asetrate=44100*0.92,aresample=44100,equalizer=f=1000:t=q:width=2:g=2,equalizer=f=2500:t=q:width=2.5:g=2.5,equalizer=f=8000:t=q:width=4:g=-3,aecho=0.8:0.88:400:0.35,aecho=0.7:0.78:600:0.25,highpass=f=40,lowpass=f=12000,volume=1.2`,
instrumentVolume: 0,
instrumentMoods: []
},
nightcore: {
name: "Nightcore",
description: "High energy with bright sparkling sounds (With Instruments)",
baseCommand: `atempo=1.30,asetrate=44100*1.18,aresample=44100,treble=g=6:f=5000:width_type=h:width=3000,bass=g=-3:f=100,equalizer=f=2000:t=q:width=2:g=2.5,equalizer=f=6000:t=q:width=3:g=4,equalizer=f=10000:t=q:width=4:g=5,acompressor=threshold=-18dB:ratio=3:attack=3:release=30:makeup=4,highpass=f=120,volume=1.4`,
instrumentVolume: 0.04,
instrumentMoods: ["upbeat", "romantic"]
},
vaporwave: {
name: "Vaporwave",
description: "Retro nostalgic with dreamy sad vibes (With Instruments)",
baseCommand: `asetrate=44100*0.82,aresample=44100,equalizer=f=300:t=q:width=2:g=4,equalizer=f=1500:t=q:width=3:g=-2,equalizer=f=6000:t=q:width=4:g=-4,aphaser=in_gain=0.5:out_gain=0.75:delay=4:decay=0.5:speed=0.2,chorus=0.6:0.85:55:0.4:0.25:2,vibrato=f=0.12:d=0.5,aecho=0.5:0.65:180:0.35,acompressor=threshold=-20dB:ratio=2.5:attack=12:release=150:makeup=2,highpass=f=70,lowpass=f=5500,volume=1.2`,
instrumentVolume: 0.005,
instrumentMoods: ["sad_romantic", "ambient", "sad"]
},
"8d_audio": {
name: "8D Audio",
description: "Immersive 360Β° with ambient dreamy layers (With Instruments)",
baseCommand: `apulsator=hz=0.08:mode=sine:width=0.9,extrastereo=m=3:c=1,aecho=0.7:0.85:100:0.35,haas=level_in=1:level_out=1.1:side_gain=0.9:middle_source=mid,bass=g=3:f=85,acompressor=threshold=-18dB:ratio=2:attack=10:release=100:makeup=2,highpass=f=60,volume=1.2`,
instrumentVolume: 0.05,
instrumentMoods: ["ambient", "romantic", "sad_romantic"]
},
// βœ… IMPROVED: Bass Boosted (smooth transitions, no noise, dynamic pulsing)
bass_boosted: {
name: "Bass Boosted",
description: "Smooth heavy bass with pulsing rhythm (Clean, no distortion)",
baseCommand: `bass=g=16:f=50:width_type=o:width=1.8,bass=g=12:f=90:width_type=h:width=100,equalizer=f=35:t=q:width=1:g=10,equalizer=f=70:t=q:width=1.5:g=8,equalizer=f=130:t=q:width=2:g=6,equalizer=f=450:t=q:width=2:g=-2.5,equalizer=f=3500:t=q:width=3.5:g=-4.5,acompressor=threshold=-24dB:ratio=6:attack=4:release=60:makeup=6,apulsator=hz=0.18:mode=sine:width=0.6,highpass=f=25,alimiter=limit=0.92:attack=6:release=65,volume=1.45`,
instrumentVolume: 0.05,
instrumentMoods: ["ambient", "chill"]
},
ambient: {
name: "Ambient Chill",
description: "Ethereal meditation with sad peaceful vibes (With Instruments)",
baseCommand: `asetrate=44100*0.94,aresample=44100,aecho=0.6:0.8:200:0.4,aecho=0.5:0.7:400:0.3,chorus=0.6:0.85:50:0.4:0.25:2,aphaser=in_gain=0.5:out_gain=0.8:delay=3:decay=0.4:speed=0.2,equalizer=f=1500:t=q:width=3:g=-2,equalizer=f=6000:t=q:width=4:g=-4,acompressor=threshold=-18dB:ratio=2:attack=15:release=200:makeup=3,highpass=f=50,lowpass=f=9000,volume=1.5`,
instrumentVolume: 0.06,
instrumentMoods: ["ambient", "sad", "sad_romantic"]
}
};
async function analyzeAudioFeatures(filePath) {
try {
console.log(' πŸ” Analyzing audio features...');
const bpm = await detectBPMAdvanced(filePath);
const spectrum = await analyzeFrequencySpectrum(filePath);
const rhythm = await analyzeRhythmPattern(filePath);
const loudness = await analyzeLoudness(filePath);
return { bpm, spectrum, rhythm, loudness };
} catch (error) {
console.error(' ⚠️ Audio analysis error:', error.message);
return null;
}
}
async function detectBPMAdvanced(filePath) {
try {
const { stdout } = await execPromise(
`ffmpeg -i "${filePath}" -t 30 -af "asetnsamples=2048,astats=metadata=1:reset=1,ametadata=print:key=lavfi.astats.Overall.RMS_level:file=-" -f null - 2>&1 | grep "pts_time" | wc -l`
);
const beatCount = parseInt(stdout.trim()) || 0;
let bpm = Math.round((beatCount / 30) * 60);
if (bpm < 60) bpm = bpm * 2;
if (bpm > 180) bpm = bpm / 2;
if (bpm < 60) bpm = 120;
if (bpm > 200) bpm = 140;
console.log(` βœ… BPM detected: ${bpm}`);
return bpm;
} catch (error) {
console.log(' ⚠️ BPM detection failed, using default 120');
return 120;
}
}
async function analyzeFrequencySpectrum(filePath) {
try {
const bands = {
subBass: { low: 20, high: 60 },
bass: { low: 60, high: 250 },
lowMid: { low: 250, high: 500 },
mid: { low: 500, high: 2000 },
highMid: { low: 2000, high: 4000 },
high: { low: 4000, high: 8000 }
};
const spectrum = {};
for (const [name, range] of Object.entries(bands)) {
const { stdout } = await execPromise(
`ffmpeg -i "${filePath}" -t 10 -af "bandpass=f=${(range.low + range.high) / 2}:width_type=h:w=${range.high - range.low},volumedetect" -f null - 2>&1 | grep "mean_volume" || echo "mean_volume: -30.0 dB"`
);
const match = stdout.match(/mean_volume:\s*([-\d.]+)/);
spectrum[name] = match ? parseFloat(match[1]) : -30.0;
}
console.log(` βœ… Spectrum: Bass=${spectrum.bass.toFixed(1)}dB, Mid=${spectrum.mid.toFixed(1)}dB`);
return spectrum;
} catch (error) {
console.log(' ⚠️ Spectrum analysis failed');
return { subBass: -25, bass: -20, lowMid: -18, mid: -15, highMid: -18, high: -20 };
}
}
async function analyzeRhythmPattern(filePath) {
try {
const { stdout } = await execPromise(
`ffmpeg -i "${filePath}" -t 20 -af "highpass=f=80,lowpass=f=250,compand=attacks=0.3:decays=0.8:points=-80/-80|-45/-15|-27/-9|0/-7|20/-7,volume=2" -f null - 2>&1 | grep "size=" || echo "size=0kB"`
);
const sizeMatch = stdout.match(/size=\s*(\d+)kB/);
const percussiveEnergy = sizeMatch ? parseInt(sizeMatch[1]) : 0;
const isHighlyRhythmic = percussiveEnergy > 500;
const isModerateRhythm = percussiveEnergy > 200;
console.log(` βœ… Rhythm: ${isHighlyRhythmic ? 'High' : isModerateRhythm ? 'Moderate' : 'Low'}`);
return { percussiveEnergy, isHighlyRhythmic, isModerateRhythm };
} catch (error) {
console.log(' ⚠️ Rhythm analysis failed');
return { percussiveEnergy: 250, isHighlyRhythmic: false, isModerateRhythm: true };
}
}
async function analyzeLoudness(filePath) {
try {
const { stdout } = await execPromise(
`ffmpeg -i "${filePath}" -t 15 -af "ebur128=framelog=verbose" -f null - 2>&1 | grep "I:" | tail -1 || echo "I: -20.0 LUFS"`
);
const match = stdout.match(/I:\s*([-\d.]+)/);
const lufs = match ? parseFloat(match[1]) : -20.0;
console.log(` βœ… Loudness: ${lufs.toFixed(1)} LUFS`);
return lufs;
} catch (error) {
console.log(' ⚠️ Loudness analysis failed');
return -20.0;
}
}
async function detectGenre(filePath) {
try {
console.log(' 🎭 Starting genre detection...');
const features = await analyzeAudioFeatures(filePath);
if (!features) {
return getDefaultGenreInfo();
}
const { bpm, spectrum, rhythm, loudness } = features;
const scores = {
hiphop: 0, pop: 0, rock: 0, edm: 0,
metal: 0, electronic: 0, rnb: 0, country: 0
};
if (bpm >= 70 && bpm <= 110) scores.hiphop += 30;
if (spectrum.bass > -15) scores.hiphop += 25;
if (spectrum.subBass > -20) scores.hiphop += 20;
if (rhythm.isHighlyRhythmic) scores.hiphop += 25;
if (bpm >= 100 && bpm <= 130) scores.pop += 30;
if (spectrum.mid > -12) scores.pop += 30;
if (Math.abs(spectrum.bass - spectrum.mid) < 8) scores.pop += 20;
if (loudness > -12) scores.pop += 20;
if (bpm >= 110 && bpm <= 150) scores.rock += 25;
if (spectrum.highMid > -15) scores.rock += 30;
if (spectrum.mid > -15) scores.rock += 20;
if (rhythm.isModerateRhythm) scores.rock += 25;
if (bpm >= 120 && bpm <= 140) scores.edm += 30;
if (bpm >= 140 && bpm <= 180) scores.edm += 20;
if (spectrum.bass > -12) scores.edm += 30;
if (rhythm.isHighlyRhythmic) scores.edm += 30;
if (spectrum.subBass > -15) scores.edm += 10;
if (bpm >= 140 && bpm <= 200) scores.metal += 35;
if (spectrum.highMid > -10) scores.metal += 25;
if (spectrum.high > -15) scores.metal += 20;
if (loudness > -10) scores.metal += 20;
if (bpm >= 100 && bpm <= 130) scores.electronic += 20;
if (spectrum.high > -15) scores.electronic += 25;
if (rhythm.isHighlyRhythmic) scores.electronic += 25;
if (spectrum.bass > -15) scores.electronic += 20;
if (bpm >= 70 && bpm <= 100) scores.rnb += 30;
if (spectrum.mid > -10) scores.rnb += 35;
if (spectrum.bass > -18) scores.rnb += 20;
if (!rhythm.isHighlyRhythmic && rhythm.isModerateRhythm) scores.rnb += 15;
if (bpm >= 90 && bpm <= 120) scores.country += 25;
if (spectrum.mid > -12) scores.country += 25;
if (spectrum.highMid > -18) scores.country += 25;
if (spectrum.bass < -20) scores.country += 15;
let detectedGenre = 'pop';
let maxScore = 0;
for (const [genre, score] of Object.entries(scores)) {
if (score > maxScore) {
maxScore = score;
detectedGenre = genre;
}
}
let confidence = 'low';
if (maxScore >= 80) confidence = 'high';
else if (maxScore >= 60) confidence = 'medium';
console.log(` βœ… DETECTED: ${detectedGenre.toUpperCase()} (${confidence} confidence, score: ${maxScore})`);
return {
genre: detectedGenre,
confidence,
bpm,
bassLevel: parseFloat(spectrum.bass.toFixed(1)),
characteristics: getGenreCharacteristics(detectedGenre),
debug: {
scores,
maxScore,
spectrum: {
bass: spectrum.bass.toFixed(1),
mid: spectrum.mid.toFixed(1),
high: spectrum.high.toFixed(1)
},
rhythm: rhythm.isHighlyRhythmic ? 'high' : rhythm.isModerateRhythm ? 'moderate' : 'low'
}
};
} catch (error) {
console.error(' ❌ Genre detection error:', error);
return getDefaultGenreInfo();
}
}
function getDefaultGenreInfo() {
return {
genre: 'pop',
confidence: 'low',
bpm: 120,
bassLevel: -20,
characteristics: 'Unable to analyze - using default',
debug: { error: 'Analysis failed' }
};
}
function getGenreCharacteristics(genre) {
const characteristics = {
pop: 'Catchy melodies, balanced mix, radio-friendly',
rock: 'Guitar-driven, energetic, band-focused',
hiphop: 'Heavy bass, rap vocals, rhythmic beats',
edm: 'Electronic beats, drops, high energy',
metal: 'Aggressive, distorted, very fast tempo',
electronic: 'Synthetic sounds, experimental, varied',
rnb: 'Smooth vocals, soul influence, groove-based',
country: 'Acoustic guitar, storytelling, folk roots'
};
return characteristics[genre] || 'Unknown style';
}
function checkGenreCompatibility(genre1Info, genre2Info) {
const compatibilityMatrix = {
pop: { pop: 95, rock: 70, hiphop: 65, edm: 75, metal: 30, electronic: 60, rnb: 70, country: 55 },
rock: { pop: 70, rock: 95, hiphop: 45, edm: 50, metal: 85, electronic: 55, rnb: 50, country: 60 },
hiphop: { pop: 65, rock: 45, hiphop: 95, edm: 70, metal: 25, electronic: 65, rnb: 80, country: 35 },
edm: { pop: 75, rock: 50, hiphop: 70, edm: 95, metal: 40, electronic: 85, rnb: 60, country: 40 },
metal: { pop: 30, rock: 85, hiphop: 25, edm: 40, metal: 95, electronic: 45, rnb: 25, country: 30 },
electronic: { pop: 60, rock: 55, hiphop: 65, edm: 85, metal: 45, electronic: 95, rnb: 55, country: 45 },
rnb: { pop: 70, rock: 50, hiphop: 80, edm: 60, metal: 25, electronic: 55, rnb: 95, country: 50 },
country: { pop: 55, rock: 60, hiphop: 35, edm: 40, metal: 30, electronic: 45, rnb: 50, country: 95 }
};
const score = compatibilityMatrix[genre1Info.genre]?.[genre2Info.genre] || 50;
const bpmDiff = Math.abs(genre1Info.bpm - genre2Info.bpm);
let adjustedScore = score;
if (bpmDiff > 40) adjustedScore -= 20;
else if (bpmDiff > 20) adjustedScore -= 10;
adjustedScore = Math.max(0, Math.min(100, adjustedScore));
let status, message, recommendation;
if (adjustedScore >= 80) {
status = 'excellent';
message = 'βœ… Excellent match! These songs will blend perfectly.';
recommendation = 'proceed';
} else if (adjustedScore >= 60) {
status = 'good';
message = 'βœ… Good compatibility. Mashup will sound professional.';
recommendation = 'proceed';
} else if (adjustedScore >= 40) {
status = 'risky';
message = '⚠️ Moderate compatibility. Result may sound experimental.';
recommendation = 'caution';
} else {
status = 'poor';
message = '❌ Poor compatibility. Strongly recommend changing one song.';
recommendation = 'change_songs';
}
return {
score: Math.round(adjustedScore),
status,
message,
recommendation,
bpmDifference: bpmDiff,
details: {
genreMatch: score >= 70 ? 'compatible' : score >= 50 ? 'moderate' : 'incompatible',
tempoMatch: bpmDiff <= 10 ? 'excellent' : bpmDiff <= 20 ? 'good' : 'poor'
}
};
}
function getSmartInstruments(effectType, count = 2) {
const effect = EFFECTS[effectType];
if (!effect || !effect.instrumentMoods) {
const instrumentKeys = Object.keys(INSTRUMENTS);
return instrumentKeys.sort(() => Math.random() - 0.5).slice(0, count);
}
const matchingInstruments = Object.keys(INSTRUMENTS).filter(key =>
effect.instrumentMoods.includes(INSTRUMENTS[key].mood)
);
return matchingInstruments.sort(() => Math.random() - 0.5).slice(0, count);
}
async function getAudioDuration(filePath) {
try {
const { stdout } = await execPromise(
`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filePath}"`
);
return parseFloat(stdout.trim());
} catch (error) {
console.error('Duration detection error:', error);
return 180;
}
}
function generateSections(duration) {
const sectionLength = 15;
const sections = [];
for (let start = 0; start < duration; start += sectionLength) {
const end = Math.min(start + sectionLength, duration);
const index = sections.length;
sections.push({
index,
type: index === 0 ? 'intro' : index === 1 ? 'verse' : index % 2 === 0 ? 'chorus' : 'verse',
startTime: parseFloat(start.toFixed(2)),
endTime: parseFloat(end.toFixed(2)),
duration: parseFloat((end - start).toFixed(2))
});
}
return sections;
}
function parseCrossfadeDuration(transitions) {
if (!transitions) return 3.0;
if (transitions === 'smooth') return 3.0;
if (transitions === 'none') return 0;
const match = transitions.match(/(\d+\.?\d*)/);
if (match) {
const duration = parseFloat(match[1]);
return Math.max(0, Math.min(10, duration));
}
return 3.0;
}
async function matchTempo(inputPath, targetBPM, currentBPM, identifier) {
try {
if (Math.abs(targetBPM - currentBPM) < 3) {
console.log(` ⏭️ Tempo match skipped (${currentBPM} β‰ˆ ${targetBPM})`);
return inputPath;
}
const ratio = targetBPM / currentBPM;
const outputPath = path.join(outputsDir, `tempo-${identifier}.wav`);
console.log(` 🎡 Matching tempo: ${currentBPM} β†’ ${targetBPM} BPM (ratio: ${ratio.toFixed(3)})`);
let tempoFilter = '';
if (ratio <= 0.5) {
tempoFilter = 'atempo=0.5,atempo=' + (ratio / 0.5).toFixed(3);
} else if (ratio >= 2.0) {
tempoFilter = 'atempo=2.0,atempo=' + (ratio / 2.0).toFixed(3);
} else {
tempoFilter = `atempo=${ratio.toFixed(3)}`;
}
await execPromise(
`ffmpeg -i "${inputPath}" -af "${tempoFilter}" -ar 44100 -y "${outputPath}"`,
{ maxBuffer: 50 * 1024 * 1024 }
);
if (!fs.existsSync(outputPath)) {
throw new Error('Tempo matched file not created');
}
console.log(` βœ… Tempo matched successfully`);
return outputPath;
} catch (error) {
console.error(` ⚠️ Tempo matching failed: ${error.message}`);
return inputPath;
}
}
// API ENDPOINTS
app.get('/', (req, res) => {
res.json({
status: 'online',
message: '🎡 Unified Mashup API v15.0 - NEW SLOWED EFFECT',
version: '15.0',
features: [
'🎭 Smart Genre Detection (8 genres)',
'🎬 CapCut-style Timeline Editor',
'🎨 8 Audio Effects (NEW: Pure Slowed)',
'πŸ”€ Natural Crossfade',
'🎹 Smart Instrument Mixing',
'🎡 BPM Detection & Tempo Matching',
'🎯 Genre Compatibility Check',
'πŸ“Š Frequency Spectrum Analysis',
'βœ‚οΈ Precise Clip Trimming',
'πŸ”Š Volume Control per Clip',
'πŸ”₯ NEW: Pure Slowed Effect (just slow tempo)',
'🎚️ IMPROVED: Clean Bass Boost (smooth pulsing)',
'❌ Zero Quality Loss'
],
total_effects: Object.keys(EFFECTS).length,
total_instruments: Object.keys(INSTRUMENTS).length,
note: 'NEW: Slowed effect added! Bass boost improved for smooth transitions!'
});
});
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
activeSessions: mashupStorage.size
});
});
app.get('/effects', (req, res) => {
res.json({
total: Object.keys(EFFECTS).length,
effects: Object.keys(EFFECTS).map(key => ({
id: key,
name: EFFECTS[key].name,
description: EFFECTS[key].description,
moods: EFFECTS[key].instrumentMoods || [],
hasInstruments: EFFECTS[key].instrumentVolume > 0,
isNew: key === 'slowed',
isImproved: key === 'bass_boosted'
}))
});
});
app.get('/instruments', (req, res) => {
res.json({
total: Object.keys(INSTRUMENTS).length,
instruments: Object.keys(INSTRUMENTS).map(key => ({
id: key,
name: INSTRUMENTS[key].name,
mood: INSTRUMENTS[key].mood
}))
});
});
app.post('/process', upload.single('audio'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ success: false, error: 'No audio file uploaded' });
}
const effectType = req.body.effect || 'lofi';
const addInstruments = req.body.instruments !== 'false';
if (!EFFECTS[effectType]) {
fs.unlinkSync(req.file.path);
return res.status(400).json({
success: false,
error: `Unknown effect: ${effectType}`,
available_effects: Object.keys(EFFECTS)
});
}
const inputPath = req.file.path;
const outputFilename = `${effectType}-${Date.now()}.mp3`;
const outputPath = path.join(outputsDir, outputFilename);
const effect = EFFECTS[effectType];
console.log(`\n🎡 Processing: ${effect.name}`);
console.log(`πŸ“ File: ${req.file.originalname}`);
let ffmpegCommand;
let usedInstruments = [];
let analysisDetails = null;
// βœ… Handle "slowed" effect (NEW)
if (effectType === 'slowed') {
console.log('\n🐌 Applying PURE SLOWED effect...');
console.log(' Speed: 80% (perfect slow)');
console.log(' Pitch: 90% (deeper tone)');
console.log(' NO REVERB, NO INSTRUMENTS');
console.log(' Just pure slowed tempo');
ffmpegCommand = `ffmpeg -i "${inputPath}" -threads 0 -af "${effect.baseCommand}" -ar 44100 -b:a 192k -preset ultrafast -y "${outputPath}"`;
analysisDetails = {
description: 'Pure slowed tempo',
speed: '80% (slower than slowed+reverb)',
pitch: '90% (deeper voice)',
reverb: 'NONE',
instruments: 'NONE',
quality: 'Simple & Clean - Just Slow'
};
}
// βœ… Handle "slowed_reverb" effect
else if (effectType === 'slowed_reverb') {
console.log('\nπŸ”₯ Applying SIMPLE Slowed + Reverb (YouTube/TikTok style)...');
console.log(' Speed: 85% (perfect slow)');
console.log(' Pitch: 92% (deep voice)');
console.log(' Vocals: Enhanced at 1000Hz & 2500Hz');
console.log(' Reverb: Clean and subtle (2 layers)');
console.log(' NO INSTRUMENTS, NO EXTRA SOUNDS');
ffmpegCommand = `ffmpeg -i "${inputPath}" -threads 0 -af "${effect.baseCommand}" -ar 44100 -b:a 192k -preset ultrafast -y "${outputPath}"`;
analysisDetails = {
description: 'Simple authentic slowed+reverb',
speed: '85% (not too slow)',
pitch: '92% (deep feel)',
vocals: 'Crystal clear (boosted at 1000Hz & 2500Hz)',
reverb: 'Subtle (2 layers - 400ms & 600ms)',
instruments: 'NONE',
quality: 'YouTube/TikTok style - Clean & Professional'
};
}
// βœ… Handle "bass_boosted" effect (IMPROVED)
else if (effectType === 'bass_boosted') {
console.log('\nπŸ”Š Applying IMPROVED BASS BOOST...');
console.log(' Bass: Heavy but clean (16dB @ 50Hz)');
console.log(' Pulsing: Smooth rhythm (0.18Hz sine wave)');
console.log(' Compression: Dynamic & artifact-free');
console.log(' NO NOISE, NO DISTORTION');
if (addInstruments && effect.instrumentVolume > 0) {
const selectedKeys = getSmartInstruments(effectType, 2);
usedInstruments = selectedKeys.map(key => ({
id: key,
name: INSTRUMENTS[key].name,
mood: INSTRUMENTS[key].mood
}));
console.log(`🎹 Instruments: ${usedInstruments.map(i => i.name).join(', ')}`);
const timestamp = Date.now();
const inst1Path = path.join(outputsDir, `inst1-${timestamp}.wav`);
const inst2Path = path.join(outputsDir, `inst2-${timestamp}.wav`);
await execPromise(`ffmpeg -f lavfi -i "${INSTRUMENTS[selectedKeys[0]].command}" -t 12 -ar 44100 -y "${inst1Path}"`);
await execPromise(`ffmpeg -f lavfi -i "${INSTRUMENTS[selectedKeys[1]].command}" -t 12 -ar 44100 -y "${inst2Path}"`);
const vol = effect.instrumentVolume;
ffmpegCommand = `ffmpeg -i "${inputPath}" -stream_loop -1 -i "${inst1Path}" -stream_loop -1 -i "${inst2Path}" -filter_complex "[0:a]volume=3.5[main];[1:a]volume=${vol}[i1];[2:a]volume=${vol}[i2];[main][i1][i2]amix=inputs=3:duration=first:dropout_transition=2:weights=15 0.5 0.5,${effect.baseCommand}" -ar 44100 -b:a 192k -preset ultrafast -threads 0 -y "${outputPath}"`;
setTimeout(() => {
[inst1Path, inst2Path].forEach(p => {
if (fs.existsSync(p)) fs.unlinkSync(p);
});
}, 5000);
} else {
console.log('⏭️ Instruments skipped for this effect');
ffmpegCommand = `ffmpeg -i "${inputPath}" -threads 0 -af "${effect.baseCommand}" -ar 44100 -b:a 192k -preset ultrafast -y "${outputPath}"`;
}
analysisDetails = {
description: 'Smooth heavy bass with pulsing',
bass: 'Deep & powerful (16dB @ 50Hz, 12dB @ 90Hz)',
pulsing: 'Smooth sine wave rhythm (0.18Hz)',
compression: 'Dynamic (6:1 ratio, clean transitions)',
quality: 'No noise, no distortion - Pure bass'
};
}
// βœ… Handle other effects
else {
if (addInstruments && effect.instrumentVolume > 0) {
const selectedKeys = getSmartInstruments(effectType, 2);
usedInstruments = selectedKeys.map(key => ({
id: key,
name: INSTRUMENTS[key].name,
mood: INSTRUMENTS[key].mood
}));
console.log(`🎹 Instruments: ${usedInstruments.map(i => i.name).join(', ')}`);
const timestamp = Date.now();
const inst1Path = path.join(outputsDir, `inst1-${timestamp}.wav`);
const inst2Path = path.join(outputsDir, `inst2-${timestamp}.wav`);
await execPromise(`ffmpeg -f lavfi -i "${INSTRUMENTS[selectedKeys[0]].command}" -t 12 -ar 44100 -y "${inst1Path}"`);
await execPromise(`ffmpeg -f lavfi -i "${INSTRUMENTS[selectedKeys[1]].command}" -t 12 -ar 44100 -y "${inst2Path}"`);
const vol = effect.instrumentVolume;
ffmpegCommand = `ffmpeg -i "${inputPath}" -stream_loop -1 -i "${inst1Path}" -stream_loop -1 -i "${inst2Path}" -filter_complex "[0:a]volume=3.5[main];[1:a]volume=${vol}[i1];[2:a]volume=${vol}[i2];[main][i1][i2]amix=inputs=3:duration=first:dropout_transition=2:weights=15 0.5 0.5,${effect.baseCommand}" -ar 44100 -b:a 192k -preset ultrafast -threads 0 -y "${outputPath}"`;
setTimeout(() => {
[inst1Path, inst2Path].forEach(p => {
if (fs.existsSync(p)) fs.unlinkSync(p);
});
}, 5000);
} else {
console.log('⏭️ Instruments skipped for this effect');
ffmpegCommand = `ffmpeg -i "${inputPath}" -threads 0 -af "${effect.baseCommand}" -ar 44100 -b:a 192k -preset ultrafast -y "${outputPath}"`;
}
}
console.log('\nβš™οΈ Processing audio...');
const startTime = Date.now();
await execPromise(ffmpegCommand, { maxBuffer: 50 * 1024 * 1024 });
const processingTime = ((Date.now() - startTime) / 1000).toFixed(2);
console.log(`βœ… Processing complete in ${processingTime}s`);
fs.unlinkSync(inputPath);
const stats = fs.statSync(outputPath);
const response = {
success: true,
message: `Processed with ${effect.name}`,
downloadUrl: `/download/${outputFilename}`,
filename: outputFilename,
effect: {
id: effectType,
name: effect.name,
description: effect.description,
moods: effect.instrumentMoods || [],
hasInstruments: effect.instrumentVolume > 0,
isNew: effectType === 'slowed',
isImproved: effectType === 'bass_boosted'
},
instruments: usedInstruments,
output: {
size_kb: (stats.size / 1024).toFixed(2),
size_mb: (stats.size / 1024 / 1024).toFixed(2)
},
processing_time_seconds: parseFloat(processingTime)
};
if (analysisDetails) {
response.analysis = analysisDetails;
}
console.log('\n═══════════════════════════════════════════════════════════');
console.log('βœ… SUCCESS - File ready for download');
console.log('═══════════════════════════════════════════════════════════\n');
res.json(response);
} catch (error) {
console.error('\n❌ Processing error:', error);
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({
success: false,
error: error.message,
details: 'Check server logs for more information'
});
}
});
app.post('/mashup/upload', upload.array('songs', 2), async (req, res) => {
try {
if (!req.files || req.files.length !== 2) {
return res.status(400).json({ success: false, error: 'Please upload exactly 2 songs' });
}
const sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const songs = req.files.map((file, index) => ({
id: `song${index + 1}`,
path: file.path,
filename: file.originalname,
size: (file.size / 1024 / 1024).toFixed(2) + ' MB'
}));
mashupStorage.set(sessionId, {
songs,
createdAt: new Date(),
analyzed: false,
status: 'uploaded'
});
console.log(`βœ… Session created: ${sessionId}`);
res.json({
success: true,
sessionId,
songs: songs.map(s => ({ id: s.id, filename: s.filename, size: s.size })),
message: 'Songs uploaded successfully. Next: analyze songs.'
});
} catch (error) {
console.error('Upload error:', error);
res.status(500).json({ success: false, error: error.message });
}
});
app.post('/mashup/analyze/:sessionId', async (req, res) => {
try {
const { sessionId } = req.params;
const session = mashupStorage.get(sessionId);
if (!session) {
return res.status(404).json({ success: false, error: 'Session not found' });
}
console.log('\n═══════════════════════════════════════════════════════════');
console.log(`πŸ”¬ ANALYZING SESSION: ${sessionId}`);
console.log('═══════════════════════════════════════════════════════════');
const analyzedSongs = [];
const genreInfos = [];
for (const song of session.songs) {
const timestamp = Date.now() + Math.random() * 1000;
console.log(`\nπŸ“€ Processing ${song.id}: ${song.filename}`);
const duration = await getAudioDuration(song.path);
console.log(` ⏱️ Duration: ${duration.toFixed(2)}s`);
const genreInfo = await detectGenre(song.path);
genreInfos.push(genreInfo);
console.log(` 🎀 Extracting vocals...`);
const vocalsPath = path.join(outputsDir, `vocals-${song.id}-${timestamp}.wav`);
await execPromise(
`ffmpeg -i "${song.path}" -af "pan=mono|c0=0.5*c0+0.5*c1,highpass=f=250,lowpass=f=4000,equalizer=f=1000:t=q:width=2:g=3,volume=1.3" -ar 44100 -y "${vocalsPath}"`,
{ maxBuffer: 50 * 1024 * 1024 }
);
console.log(' βœ… Vocals extracted');
console.log(' 🎸 Extracting instruments...');
const instrumentsPath = path.join(outputsDir, `instruments-${song.id}-${timestamp}.wav`);
await execPromise(
`ffmpeg -i "${song.path}" -af "extrastereo=m=2.8,equalizer=f=1000:t=q:width=3:g=-4,highpass=f=40,lowpass=f=15000,volume=1.1" -ar 44100 -y "${instrumentsPath}"`,
{ maxBuffer: 50 * 1024 * 1024 }
);
console.log(' βœ… Instruments extracted');
const sections = generateSections(duration);
analyzedSongs.push({
id: song.id,
filename: song.filename,
duration: parseFloat(duration.toFixed(2)),
bpm: genreInfo.bpm,
genre: genreInfo.genre,
genreConfidence: genreInfo.confidence,
bassLevel: genreInfo.bassLevel,
genreCharacteristics: genreInfo.characteristics,
vocals: vocalsPath,
instruments: instrumentsPath,
sections
});
}
const avgBPM = Math.round((genreInfos[0].bpm + genreInfos[1].bpm) / 2);
const compatibility = checkGenreCompatibility(genreInfos[0], genreInfos[1]);
console.log(`\n🎯 Compatibility: ${compatibility.score}% (${compatibility.status})`);
session.analyzed = true;
session.analyzedData = analyzedSongs;
session.targetBPM = avgBPM;
session.compatibility = compatibility;
session.genreInfos = genreInfos;
session.status = 'analyzed';
mashupStorage.set(sessionId, session);
console.log('\n═══════════════════════════════════════════════════════════');
console.log('βœ… ANALYSIS COMPLETE');
console.log('═══════════════════════════════════════════════════════════\n');
res.json({
success: true,
message: 'Songs analyzed successfully',
targetBPM: avgBPM,
compatibility: compatibility,
songs: analyzedSongs.map(s => ({
id: s.id,
filename: s.filename,
duration: s.duration,
bpm: s.bpm,
genre: s.genre,
genreConfidence: s.genreConfidence,
genreCharacteristics: s.genreCharacteristics,
sections: s.sections
})),
warnings: compatibility.recommendation === 'change_songs' ? [{
type: 'genre_mismatch',
severity: 'high',
message: compatibility.message,
suggestion: `Song 1 is ${genreInfos[0].genre.toUpperCase()} (${genreInfos[0].bpm} BPM), Song 2 is ${genreInfos[1].genre.toUpperCase()} (${genreInfos[1].bpm} BPM). Consider uploading songs from similar genres.`
}] : compatibility.recommendation === 'caution' ? [{
type: 'moderate_compatibility',
severity: 'medium',
message: compatibility.message,
suggestion: 'Mashup will work but may sound experimental.'
}] : [],
nextStep: 'create'
});
} catch (error) {
console.error('\n❌ Analysis error:', error);
res.status(500).json({ success: false, error: error.message });
}
});
app.post('/mashup/create', async (req, res) => {
const tempFiles = [];
try {
const { session_id, sessionId, arrangement, theme, transitions, forceCreate } = req.body;
const actualSessionId = session_id || sessionId;
console.log('\n🎨 CREATE MASHUP REQUEST');
console.log('Session ID received (snake_case):', session_id);
console.log('Session ID received (camelCase):', sessionId);
console.log('Using Session ID:', actualSessionId);
console.log('Clips in arrangement:', arrangement?.length);
if (!actualSessionId) {
console.error('❌ No session ID provided!');
return res.status(400).json({
success: false,
error: 'Session ID is required',
received: { session_id, sessionId }
});
}
if (!arrangement || !Array.isArray(arrangement) || arrangement.length === 0) {
return res.status(400).json({
success: false,
error: 'Arrangement array is required'
});
}
const session = mashupStorage.get(actualSessionId);
if (!session) {
console.error(`❌ Session not found: ${actualSessionId}`);
console.log('Available sessions:', Array.from(mashupStorage.keys()));
return res.status(404).json({
success: false,
error: 'Session not found',
sessionId: actualSessionId
});
}
if (!session.analyzed || !session.analyzedData) {
return res.status(400).json({
success: false,
error: 'Session not analyzed. Call /mashup/analyze first.'
});
}
if (session.compatibility && session.compatibility.recommendation === 'change_songs' && !forceCreate) {
console.warn('⚠️ Songs not compatible - rejecting');
return res.status(400).json({
success: false,
error: 'Songs are not compatible',
compatibility: session.compatibility,
message: session.compatibility.message,
suggestion: 'Upload different songs or set forceCreate: true'
});
}
const crossfadeDuration = parseCrossfadeDuration(transitions);
const timestamp = Date.now();
const segmentPaths = [];
console.log(`\n🎨 Creating mashup: ${arrangement.length} clips`);
console.log(` Target BPM: ${session.targetBPM}`);
console.log(` Crossfade: ${crossfadeDuration}s`);
console.log(` Theme: ${theme || 'none'}`);
for (let i = 0; i < arrangement.length; i++) {
const section = arrangement[i];
const song = session.analyzedData.find(s => s.id === section.songId);
if (!song) {
console.warn(`⚠️ Skipping clip ${i}: song not found`);
continue;
}
console.log(`\nπŸ“ Clip ${i + 1}/${arrangement.length}: ${song.id}`);
const vocalsMatched = await matchTempo(
song.vocals,
session.targetBPM,
song.bpm,
`${timestamp}-${i}-vocals`
);
const instrumentsMatched = await matchTempo(
song.instruments,
session.targetBPM,
song.bpm,
`${timestamp}-${i}-inst`
);
tempFiles.push(vocalsMatched, instrumentsMatched);
const segmentPath = path.join(outputsDir, `segment-${timestamp}-${i}.wav`);
let inputs = [];
let filterParts = [];
if (section.parts.includes('vocals')) {
inputs.push(`-i "${vocalsMatched}"`);
filterParts.push('[0:a]');
}
if (section.parts.includes('instruments')) {
const idx = inputs.length;
inputs.push(`-i "${instrumentsMatched}"`);
filterParts.push(`[${idx}:a]`);
}
if (inputs.length === 0) continue;
const volume = section.volume || 1.0;
let filterComplex = '';
if (filterParts.length > 1) {
filterComplex = `${filterParts.join('')}amix=inputs=${filterParts.length}:duration=longest:normalize=0,atrim=${section.startTime}:${section.endTime},asetpts=PTS-STARTPTS,volume=${volume}`;
} else {
filterComplex = `${filterParts[0]}atrim=${section.startTime}:${section.endTime},asetpts=PTS-STARTPTS,volume=${volume}`;
}
await execPromise(
`ffmpeg ${inputs.join(' ')} -filter_complex "${filterComplex}" -ar 44100 -y "${segmentPath}"`,
{ maxBuffer: 50 * 1024 * 1024 }
);
segmentPaths.push(segmentPath);
tempFiles.push(segmentPath);
console.log(` βœ… Segment created`);
}
if (segmentPaths.length === 0) {
throw new Error('No segments created');
}
console.log(`\nπŸ”— Joining ${segmentPaths.length} segments...`);
let finalAudioPath = segmentPaths[0];
if (crossfadeDuration > 0 && segmentPaths.length > 1) {
for (let i = 1; i < segmentPaths.length; i++) {
const outputPath = path.join(outputsDir, `crossfade-${timestamp}-${i}.wav`);
await execPromise(
`ffmpeg -i "${finalAudioPath}" -i "${segmentPaths[i]}" -filter_complex "[0:a][1:a]acrossfade=d=${crossfadeDuration}:o=1:c1=tri:c2=tri" -ar 44100 -ac 2 -preset ultrafast -threads 0 -y "${outputPath}"`,
{ maxBuffer: 50 * 1024 * 1024 }
);
finalAudioPath = outputPath;
tempFiles.push(outputPath);
}
}
const outputFilename = `mashup-${timestamp}.mp3`;
const finalPath = path.join(outputsDir, outputFilename);
let masteringFilter = `equalizer=f=80:t=q:width=1.5:g=1.5,equalizer=f=5000:t=q:width=3:g=1,highpass=f=30`;
if (theme && EFFECTS[theme]) {
console.log(`\n🎨 Applying theme: ${EFFECTS[theme].name}`);
masteringFilter = EFFECTS[theme].baseCommand + ',' + masteringFilter;
}
console.log(`\n🎧 Final mastering...`);
await execPromise(
`ffmpeg -i "${finalAudioPath}" -af "${masteringFilter}" -ar 44100 -b:a 192k -ac 2 -preset ultrafast -threads 0 -y "${finalPath}"`,
{ maxBuffer: 50 * 1024 * 1024 }
);
const stats = fs.statSync(finalPath);
console.log(`βœ… Mashup created: ${outputFilename}`);
setTimeout(() => {
tempFiles.forEach(f => {
if (fs.existsSync(f)) {
try { fs.unlinkSync(f); } catch (e) {}
}
});
}, 15000);
res.json({
success: true,
message: 'Mashup created successfully',
downloadUrl: `/download/${outputFilename}`,
filename: outputFilename,
compatibility: session.compatibility,
details: {
clipsUsed: arrangement.length,
targetBPM: session.targetBPM,
crossfadeDuration: crossfadeDuration,
theme: theme || 'none',
size_kb: (stats.size / 1024).toFixed(2),
size_mb: (stats.size / 1024 / 1024).toFixed(2),
quality: session.compatibility?.score >= 60 ? 'excellent' : 'experimental'
}
});
console.log('\n═══════════════════════════════════════════════════════════');
console.log('βœ… MASHUP COMPLETE');
console.log('═══════════════════════════════════════════════════════════\n');
} catch (error) {
console.error('\n❌ ERROR:', error);
tempFiles.forEach(f => {
if (fs.existsSync(f)) {
try { fs.unlinkSync(f); } catch (e) {}
}
});
res.status(500).json({ success: false, error: error.message });
}
});
app.get('/mashup/session/:sessionId', (req, res) => {
try {
const session = mashupStorage.get(req.params.sessionId);
if (!session) {
return res.status(404).json({ success: false, error: 'Session not found' });
}
res.json({
success: true,
session: {
id: req.params.sessionId,
status: session.status,
analyzed: session.analyzed,
targetBPM: session.targetBPM || null,
compatibility: session.compatibility || null,
createdAt: session.createdAt
}
});
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
app.get('/download/:filename', (req, res) => {
const filePath = path.join(outputsDir, req.params.filename);
if (!fs.existsSync(filePath)) {
return res.status(404).json({ success: false, error: 'File not found' });
}
res.download(filePath, req.params.filename, (err) => {
if (!err) {
setTimeout(() => {
if (fs.existsSync(filePath)) {
try { fs.unlinkSync(filePath); } catch (e) {}
}
}, 10000);
}
});
});
setInterval(() => {
const now = Date.now();
for (const [sessionId, session] of mashupStorage.entries()) {
if (now - session.createdAt.getTime() > 7200000) {
mashupStorage.delete(sessionId);
console.log(`🧹 Cleaned old session: ${sessionId}`);
}
}
}, 600000);
app.listen(PORT, '0.0.0.0', () => {
console.log('\n═══════════════════════════════════════════════════════════');
console.log('🎡 UNIFIED MASHUP API v15.0 - NEW SLOWED EFFECT');
console.log('═══════════════════════════════════════════════════════════');
console.log(`πŸ“ Server: http://0.0.0.0:${PORT}`);
console.log('');
console.log('πŸ”₯ NEW FEATURES:');
console.log('═══════════════════════════════════════════════════════════');
console.log('βœ… NEW: Pure "Slowed" effect (just slow tempo)');
console.log('βœ… IMPROVED: Clean Bass Boost (smooth pulsing)');
console.log('βœ… 8 Total effects with smart instruments');
console.log('βœ… Genre detection & compatibility check');
console.log('βœ… Timeline mashup editor');
console.log('');
console.log('πŸ“‹ ENDPOINTS:');
console.log('═══════════════════════════════════════════════════════════');
console.log('GET / - API Info');
console.log('GET /health - Health Check');
console.log('GET /effects - List all effects (includes new slowed)');
console.log('POST /process - Single song processing');
console.log('POST /mashup/upload - Upload 2 songs');
console.log('POST /mashup/analyze/:sessionId - Analyze');
console.log('POST /mashup/create - Create mashup');
console.log('GET /download/:filename - Download');
console.log('═══════════════════════════════════════════════════════════');
console.log('');
console.log('🎨 AVAILABLE EFFECTS:');
console.log('═══════════════════════════════════════════════════════════');
console.log('1. lofi - LoFi Hip Hop');
console.log('2. slowed πŸ†• - Pure Slowed (just slow tempo)');
console.log('3. slowed_reverb - Slowed + Reverb');
console.log('4. nightcore - Nightcore');
console.log('5. vaporwave - Vaporwave');
console.log('6. 8d_audio - 8D Audio');
console.log('7. bass_boosted ⭐ - Bass Boost (IMPROVED)');
console.log('8. ambient - Ambient Chill');
console.log('═══════════════════════════════════════════════════════════\n');
});