/** * AutoMixer Tests * * Basic test suite for the automixer library */ import { describe, it } from 'node:test'; import assert from 'node:assert'; import { AutoMixer, BPMDetector, AudioAnalyzer, PitchShifter } from '../src/index.js'; describe('AutoMixer', () => { it('should create an instance with default options', () => { const mixer = new AutoMixer(); assert.ok(mixer); assert.strictEqual(mixer.options.crossfadeDuration, 8); assert.strictEqual(mixer.options.preservePitch, true); }); it('should create an instance with custom options', () => { const mixer = new AutoMixer({ crossfadeDuration: 12, targetBPM: 128, preservePitch: false }); assert.strictEqual(mixer.options.crossfadeDuration, 12); assert.strictEqual(mixer.options.targetBPM, 128); assert.strictEqual(mixer.options.preservePitch, false); }); it('should add and clear tracks', () => { const mixer = new AutoMixer(); mixer.addTracks(['track1.mp3', 'track2.mp3']); assert.strictEqual(mixer.tracks.length, 2); mixer.addTracks(['track3.mp3']); assert.strictEqual(mixer.tracks.length, 3); mixer.clearTracks(); assert.strictEqual(mixer.tracks.length, 0); }); it('should calculate tempo ratio correctly', () => { const mixer = new AutoMixer({ maxBPMChange: 10 }); // Simple ratio const ratio1 = mixer.calculateTempoRatio(120, 132); assert.strictEqual(ratio1, 1.1); // Identity const ratio2 = mixer.calculateTempoRatio(128, 128); assert.strictEqual(ratio2, 1); }); it('should throw error when analyzing without tracks', async () => { const mixer = new AutoMixer(); await assert.rejects( async () => mixer.createMix('output.mp3'), { message: 'No tracks analyzed. Call analyzeTracks() first.' } ); }); }); describe('BPMDetector', () => { it('should create an instance with default options', () => { const detector = new BPMDetector(); assert.ok(detector); assert.strictEqual(detector.options.minBPM, 60); assert.strictEqual(detector.options.maxBPM, 200); }); it('should normalize BPM correctly', () => { const detector = new BPMDetector({ minBPM: 60, maxBPM: 200 }); // Normal BPM assert.strictEqual(detector.normalizeBPM(120), 120); // Half BPM (should double) assert.strictEqual(detector.normalizeBPM(55), 110); // Double BPM (should halve) assert.strictEqual(detector.normalizeBPM(240), 120); }); it('should extrapolate beats correctly', () => { const detector = new BPMDetector(); // 120 BPM = 0.5s per beat const beats = detector.extrapolateBeats([0, 0.5, 1.0], 120, 5); assert.ok(beats.length > 0); assert.ok(beats.every(b => b >= 0 && b < 5)); }); }); describe('AudioAnalyzer', () => { it('should create an instance', () => { const analyzer = new AudioAnalyzer(); assert.ok(analyzer); }); it('should have cache management', () => { const analyzer = new AudioAnalyzer(); analyzer.clearCache(); assert.strictEqual(analyzer.ffprobeCache.size, 0); }); }); describe('PitchShifter', () => { it('should create an instance with default options', () => { const shifter = new PitchShifter(); assert.ok(shifter); assert.strictEqual(shifter.options.outputFormat, 'mp3'); assert.strictEqual(shifter.options.outputBitrate, 320); }); it('should build atempo chain correctly', () => { const shifter = new PitchShifter(); // Simple ratio within range const chain1 = shifter.buildAtempoChain(1.5); assert.strictEqual(chain1, 'atempo=1.5'); // Ratio above 2.0 (needs chaining) const chain2 = shifter.buildAtempoChain(3.0); assert.ok(chain2.includes('atempo=2.0')); assert.ok(chain2.includes('atempo=1.5')); // Ratio below 0.5 (needs chaining) const chain3 = shifter.buildAtempoChain(0.25); assert.ok(chain3.includes('atempo=0.5')); }); }); describe('Module Exports', () => { it('should export all components', async () => { const module = await import('../src/index.js'); assert.ok(module.AutoMixer); assert.ok(module.BPMDetector); assert.ok(module.AudioAnalyzer); assert.ok(module.TrackMixer); assert.ok(module.PitchShifter); assert.ok(module.default); }); });