generate_variations
Creates multiple audio variations from one file by adjusting pitch, volume, and spectral characteristics to build sound families for batch processing.
Instructions
Generate multiple variations of a sound from a single input - perfect for creating sound families
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| inputFile | Yes | Path to input audio file | |
| outputDirectory | Yes | Directory for output variations | |
| count | No | Number of variations to generate (1-20) | |
| pitchRange | No | Pitch variation range in semitones (±) | |
| volumeRange | No | Volume variation range in dB (±) | |
| spectralRange | No | Spectral variation range in dB (±) | |
| seed | No | Random seed for reproducible variations | |
| overwrite | No | Whether to overwrite existing output files |
Implementation Reference
- Core handler function that generates multiple audio file variations by applying randomized pitch shifts, spectral changes, and other effects using FFmpeg.async generateVariations( inputFile: string, outputDirectory: string, variations: VariationOperation, baseOperations?: AudioOperations, overwrite: boolean = false ): Promise<ProcessingResult[]> { const results: ProcessingResult[] = []; try { await validateInputFile(inputFile); await ensureOutputDirectory(outputDirectory); const seed = variations.seed || Math.floor(Math.random() * 1000000); const rng = this.createSeededRandom(seed); for (let i = 0; i < variations.count; i++) { const variationOps: AudioOperations = { ...baseOperations, advanced: { ...baseOperations?.advanced, ...this.generateRandomVariation(variations, rng) } }; const outputFile = path.join(outputDirectory, `${path.parse(inputFile).name}_var${i + 1}${path.extname(inputFile)}` ); const result = await this.processAudioFile( inputFile, outputFile, variationOps, overwrite ); results.push(result); } return results; } catch (error) { throw new FFmpegError(`Variation generation failed: ${(error as Error).message}`); } }
- src/tools/index.ts:203-258 (schema)Tool definition including input schema for validating parameters like inputFile, outputDirectory, count, pitchRange, etc.export const generateVariationsTool: Tool = { name: 'generate_variations', description: 'Generate multiple variations of a sound from a single input - perfect for creating sound families', inputSchema: { type: 'object', properties: { inputFile: { type: 'string', description: 'Path to input audio file' }, outputDirectory: { type: 'string', description: 'Directory for output variations' }, count: { type: 'number', description: 'Number of variations to generate (1-20)', minimum: 1, maximum: 20, default: 5 }, pitchRange: { type: 'number', description: 'Pitch variation range in semitones (±)', minimum: 0, maximum: 12, default: 2 }, volumeRange: { type: 'number', description: 'Volume variation range in dB (±)', minimum: 0, maximum: 10, default: 3 }, spectralRange: { type: 'number', description: 'Spectral variation range in dB (±)', minimum: 0, maximum: 6, default: 2 }, seed: { type: 'number', description: 'Random seed for reproducible variations', optional: true }, overwrite: { type: 'boolean', description: 'Whether to overwrite existing output files', default: false } }, required: ['inputFile', 'outputDirectory'] } };
- src/tools/index.ts:593-623 (registration)MCP tool request handler case that dispatches 'generate_variations' calls to the AdvancedAudioProcessor.generateVariations method.case 'generate_variations': { const input = args as any; const variations = { count: input.count || 5, pitchRange: input.pitchRange || 2, volumeRange: input.volumeRange || 3, spectralRange: input.spectralRange || 2, seed: input.seed }; const results = await advancedProcessor.generateVariations( input.inputFile, input.outputDirectory, variations, undefined, input.overwrite || false ); return { content: [ { type: 'text', text: JSON.stringify({ success: true, variationsGenerated: results.length, results: results }, null, 2) } ] }; }
- src/tools/index.ts:732-742 (registration)Array of all available tools including generateVariationsTool, used for tool listing/discovery.export const tools = [ processAudioFileTool, batchProcessAudioTool, applyPresetTool, listPresetsTool, getQueueStatusTool, generateVariationsTool, createHarmonicsTool, advancedProcessTool, layerSoundsTool ];
- Helper method that generates randomized audio effect parameters for each variation based on specified ranges.} /** * Layer multiple sounds together with advanced blending */ async layerSounds( inputFiles: string[], outputFile: string, layering: LayeringOperation, overwrite: boolean = false ): Promise<ProcessingResult> { const startTime = Date.now(); try { if (inputFiles.length === 0) { throw new FFmpegError('No input files provided for layering'); } // Validate all input files for (const file of inputFiles) { await validateInputFile(file); } await ensureOutputDirectory(outputFile); await handleExistingOutput(outputFile, overwrite); const command = ffmpeg(); // Add all input files inputFiles.forEach(file => command.input(file)); // Build complex filter for layering const filterGraph = this.buildLayeringFilter(inputFiles, layering); command.complexFilter(filterGraph); // Execute command await executeFFmpegCommand(command, outputFile); return { success: true, inputFile: inputFiles.join(', '), outputFile, processingTime: Date.now() - startTime, operations: { advanced: { layering } } }; } catch (error) { return { success: false, inputFile: inputFiles.join(', '), outputFile, processingTime: Date.now() - startTime, operations: { advanced: { layering } }, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Create harmonic variations by adding octaves and intervals */ async createHarmonicVariations( inputFile: string, outputDirectory: string, harmonics: HarmonicsOperation, overwrite: boolean = false ): Promise<ProcessingResult[]> { const results: ProcessingResult[] = []; const baseName = path.parse(inputFile).name; const harmonicIntervals = [ { name: 'octave_up', semitones: 12, mix: harmonics.octaveUp }, { name: 'octave_down', semitones: -12, mix: harmonics.octaveDown }, { name: 'fifth_up', semitones: 7, mix: harmonics.fifthUp }, { name: 'third_up', semitones: 4, mix: harmonics.thirdUp } ]; for (const interval of harmonicIntervals) { if (interval.mix && interval.mix > 0) { const outputFile = path.join(outputDirectory, `${baseName}_${interval.name}${path.extname(inputFile)}` ); const operations: AudioOperations = { advanced: { pitch: { semitones: interval.semitones }, layering: { layers: [ { blend: 'mix', volume: 1 - interval.mix }, { blend: 'add', volume: interval.mix, pitch: interval.semitones } ] } } }; const result = await this.processAudioFile( inputFile, outputFile, operations, overwrite ); results.push(result); } } return results; } /** * Override the base applyOperationsToCommand to include advanced effects */ protected applyOperationsToCommand(command: any, operations: AudioOperations): void { // Apply base operations first (volume, format, basic effects) super.applyOperationsToCommand(command, operations); // Apply advanced operations if (operations.advanced) { this.applyAdvancedOperations(command, operations.advanced); } } /** * Apply advanced operations to FFmpeg command */ protected applyAdvancedOperations(command: any, advanced: AdvancedEffectsOperation): void { const filters: string[] = []; // Pitch shifting if (advanced.pitch) { filters.push(this.buildPitchFilter(advanced.pitch)); } // Tempo adjustment if (advanced.tempo) { filters.push(this.buildTempoFilter(advanced.tempo)); } // Spectral processing if (advanced.spectral) { const spectralFilter = this.buildSpectralFilter(advanced.spectral); if (spectralFilter) { filters.push(spectralFilter); } } // Dynamics processing if (advanced.dynamics) { filters.push(...this.buildDynamicsFilters(advanced.dynamics)); } // Spatial processing if (advanced.spatial) { filters.push(...this.buildSpatialFilters(advanced.spatial)); } // Modulation effects if (advanced.modulation) { filters.push(...this.buildModulationFilters(advanced.modulation)); } // Apply all filters if (filters.length > 0) { command.audioFilters(filters); } } /** * Build pitch shifting filter */ private buildPitchFilter(pitch: PitchOperation): string { const totalCents = (pitch.semitones * 100) + (pitch.cents || 0); const ratio = Math.pow(2, totalCents / 1200); if (pitch.preserveFormants) { return `asetrate=44100*${ratio},aresample=44100,atempo=${1/ratio}`; } else { return `asetrate=44100*${ratio},aresample=44100`; } } /** * Build tempo adjustment filter */ private buildTempoFilter(tempo: TempoOperation): string { if (tempo.preservePitch) { return `atempo=${tempo.factor}`; } else { return `asetrate=44100*${tempo.factor},aresample=44100`; } } /** * Build spectral processing filter */ private buildSpectralFilter(spectral: SpectralOperation): string { const eqBands: string[] = []; if (spectral.bassBoost !== undefined) { eqBands.push(`bass=g=${spectral.bassBoost}`); } if (spectral.trebleBoost !== undefined) { eqBands.push(`treble=g=${spectral.trebleBoost}`); } if (spectral.midCut !== undefined) { eqBands.push(`equalizer=f=1000:width=500:g=${-Math.abs(spectral.midCut)}`); } if (spectral.warmth !== undefined) { // Add subtle low-mid boost for warmth const warmthGain = spectral.warmth * 3; eqBands.push(`equalizer=f=200:width=100:g=${warmthGain}`); } if (spectral.brightness !== undefined) { // Add high frequency presence const brightnessGain = spectral.brightness * 4; eqBands.push(`equalizer=f=8000:width=2000:g=${brightnessGain}`); } return eqBands.join(','); } /** * Build dynamics processing filters */ private buildDynamicsFilters(dynamics: DynamicsOperation): string[] { const filters: string[] = []; if (dynamics.compressor) { const comp = dynamics.compressor; filters.push( `acompressor=threshold=${comp.threshold}dB:ratio=${comp.ratio}:attack=${comp.attack}:release=${comp.release}${comp.knee ? `:knee=${comp.knee}` : ''}` ); } if (dynamics.gate) { const gate = dynamics.gate; filters.push( `agate=threshold=${gate.threshold}dB:ratio=${gate.ratio}${gate.attack ? `:attack=${gate.attack}` : ''}${gate.release ? `:release=${gate.release}` : ''}` ); } if (dynamics.limiter) { const limiter = dynamics.limiter; filters.push( `alimiter=limit=${limiter.threshold}dB${limiter.release ? `:release=${limiter.release}` : ''}` ); } return filters; } /** * Build spatial processing filters */ private buildSpatialFilters(spatial: SpatialOperation): string[] { const filters: string[] = []; if (spatial.stereoWidth !== undefined) { filters.push(`extrastereo=m=${spatial.stereoWidth}`); } if (spatial.panPosition !== undefined) { filters.push(`pan=stereo|c0=${1 - Math.abs(Math.min(0, spatial.panPosition))}*c0+${Math.max(0, -spatial.panPosition)}*c1|c1=${1 - Math.abs(Math.max(0, spatial.panPosition))}*c1+${Math.max(0, spatial.panPosition)}*c0`); } if (spatial.delayTime !== undefined) { const delaySeconds = spatial.delayTime / 1000; const feedback = spatial.delayFeedback || 0.3; filters.push(`adelay=${spatial.delayTime}|${spatial.delayTime}`); if (feedback > 0) { filters.push(`afeedback=feedback=${feedback}`); } } if (spatial.reverbSend !== undefined) { // Simple reverb using allpass filters filters.push(`aecho=0.8:0.9:${Math.floor(spatial.reverbSend * 1000)}:${spatial.reverbSend}`); } return filters; } /** * Build modulation effect filters */ private buildModulationFilters(modulation: ModulationOperation): string[] { const filters: string[] = []; if (modulation.tremolo) { const trem = modulation.tremolo; filters.push(`tremolo=f=${trem.rate}:d=${trem.depth}`); } if (modulation.vibrato) { const vib = modulation.vibrato; filters.push(`vibrato=f=${vib.rate}:d=${vib.depth}`); } if (modulation.chorus) { const chorus = modulation.chorus; const delayMs = chorus.delay; filters.push(`chorus=0.7:0.9:${delayMs}:0.25:${chorus.rate}:${chorus.depth}:t`); } return filters; } /** * Build layering filter for complex mixing */ private buildLayeringFilter(inputFiles: string[], layering: LayeringOperation): string { const layers = layering.layers; let filterGraph = ''; // Apply individual layer processing layers.forEach((layer, i) => { if (i < inputFiles.length) { let layerFilter = `[${i}:a]`; // Apply layer-specific effects if (layer.delay) { layerFilter += `adelay=${layer.delay}[delayed${i}];[delayed${i}]`; } if (layer.pitch) { const ratio = Math.pow(2, layer.pitch / 12); layerFilter += `asetrate=44100*${ratio},aresample=44100[pitched${i}];[pitched${i}]`; } if (layer.volume !== undefined) { layerFilter += `volume=${layer.volume}[vol${i}];[vol${i}]`; } if (layer.pan !== undefined) { layerFilter += `pan=stereo|c0=${1 - Math.abs(Math.min(0, layer.pan))}*c0+${Math.max(0, -layer.pan)}*c1|c1=${1 - Math.abs(Math.max(0, layer.pan))}*c1+${Math.max(0, layer.pan)}*c0[pan${i}];[pan${i}]`; } filterGraph += layerFilter + `[processed${i}];`; } }); // Mix all layers together const mixInputs = layers.map((_, i) => `[processed${i}]`).join(''); filterGraph += `${mixInputs}amix=inputs=${layers.length}:duration=longest:dropout_transition=2[final]`; return filterGraph; } /** * Generate random variation parameters */ private generateRandomVariation(variations: VariationOperation, rng: () => number): AdvancedEffectsOperation {