sfxmix 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo/{demo_7.mjs → demo_5.mjs} +3 -3
- package/demo/demo_6.mjs +3 -3
- package/index.js +255 -224
- package/package.json +3 -2
|
@@ -4,10 +4,10 @@ const sfx = new SfxMix();
|
|
|
4
4
|
// Slow down part1.mp3 by 25%
|
|
5
5
|
await sfx
|
|
6
6
|
.add('part1.mp3')
|
|
7
|
-
.filter('
|
|
8
|
-
.save('
|
|
7
|
+
.filter('tempo', { x: 0.75 })
|
|
8
|
+
.save('demo5_part1_slow.mp3')
|
|
9
9
|
.then(() => {
|
|
10
|
-
console.log('Successfully exported:
|
|
10
|
+
console.log('Successfully exported: demo5_part1_slow.mp3');
|
|
11
11
|
})
|
|
12
12
|
.catch((error) => {
|
|
13
13
|
console.error('Error during audio processing:', error);
|
package/demo/demo_6.mjs
CHANGED
|
@@ -4,10 +4,10 @@ const sfx = new SfxMix();
|
|
|
4
4
|
// Slow down part1.mp3 by 25%
|
|
5
5
|
await sfx
|
|
6
6
|
.add('part1.mp3')
|
|
7
|
-
.filter('
|
|
8
|
-
.save('
|
|
7
|
+
.filter('echo', { delays: [1000, 2000, 3000, 4000], decays: [0.5, 0.5, 0.5, 0.5] })
|
|
8
|
+
.save('demo6_part1_echo.mp3')
|
|
9
9
|
.then(() => {
|
|
10
|
-
console.log('Successfully exported:
|
|
10
|
+
console.log('Successfully exported: demo6_part1_echo.mp3');
|
|
11
11
|
})
|
|
12
12
|
.catch((error) => {
|
|
13
13
|
console.error('Error during audio processing:', error);
|
package/index.js
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
const ffmpeg = require('fluent-ffmpeg');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
3
4
|
const { v4: uuidv4 } = require('uuid');
|
|
4
5
|
|
|
5
6
|
class SfxMix {
|
|
6
|
-
constructor() {
|
|
7
|
+
constructor(config = {}) {
|
|
7
8
|
this.actions = [];
|
|
8
|
-
this.currentFile = null;
|
|
9
|
+
this.currentFile = null;
|
|
10
|
+
this.TMP_DIR = path.resolve(config.tmpDir || path.join(__dirname, 'tmp'));
|
|
11
|
+
|
|
12
|
+
// Ensure the temporary directory exists and is writable
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.existsSync(this.TMP_DIR)) {
|
|
15
|
+
fs.mkdirSync(this.TMP_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
fs.accessSync(this.TMP_DIR, fs.constants.W_OK);
|
|
18
|
+
// console.log(`Temporary directory set to: ${this.TMP_DIR}`);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.error(`Error accessing temporary directory: ${this.TMP_DIR}`);
|
|
21
|
+
console.error(err);
|
|
22
|
+
throw new Error('Unable to access or create temporary directory');
|
|
23
|
+
}
|
|
9
24
|
}
|
|
10
25
|
|
|
11
26
|
add(input) {
|
|
@@ -41,9 +56,9 @@ class SfxMix {
|
|
|
41
56
|
if (this.currentFile == null) {
|
|
42
57
|
this.currentFile = action.input;
|
|
43
58
|
} else {
|
|
44
|
-
const tempFile = `temp_concat_${uuidv4()}.mp3
|
|
45
|
-
await concatenateAudioFiles([this.currentFile, action.input], tempFile);
|
|
46
|
-
if (isTempFile(this.currentFile)) {
|
|
59
|
+
const tempFile = path.join(this.TMP_DIR, `temp_concat_${uuidv4()}.mp3`);
|
|
60
|
+
await this.concatenateAudioFiles([this.currentFile, action.input], tempFile);
|
|
61
|
+
if (this.isTempFile(this.currentFile)) {
|
|
47
62
|
fs.unlinkSync(this.currentFile);
|
|
48
63
|
}
|
|
49
64
|
this.currentFile = tempFile;
|
|
@@ -53,22 +68,22 @@ class SfxMix {
|
|
|
53
68
|
if (this.currentFile == null) {
|
|
54
69
|
throw new Error('No audio to mix with. Add or concatenate audio before mixing.');
|
|
55
70
|
}
|
|
56
|
-
const tempFile = `temp_mix_${uuidv4()}.mp3
|
|
57
|
-
await mixAudioFiles(this.currentFile, action.input, tempFile, action.options);
|
|
58
|
-
if (isTempFile(this.currentFile)) {
|
|
71
|
+
const tempFile = path.join(this.TMP_DIR, `temp_mix_${uuidv4()}.mp3`);
|
|
72
|
+
await this.mixAudioFiles(this.currentFile, action.input, tempFile, action.options);
|
|
73
|
+
if (this.isTempFile(this.currentFile)) {
|
|
59
74
|
fs.unlinkSync(this.currentFile);
|
|
60
75
|
}
|
|
61
76
|
this.currentFile = tempFile;
|
|
62
77
|
} else if (action.type === 'silence') {
|
|
63
78
|
// Existing silence logic
|
|
64
|
-
const tempSilenceFile = `temp_silence_${uuidv4()}.mp3
|
|
65
|
-
await generateSilence(action.duration, tempSilenceFile);
|
|
79
|
+
const tempSilenceFile = path.join(this.TMP_DIR, `temp_silence_${uuidv4()}.mp3`);
|
|
80
|
+
await this.generateSilence(action.duration, tempSilenceFile);
|
|
66
81
|
if (this.currentFile == null) {
|
|
67
82
|
this.currentFile = tempSilenceFile;
|
|
68
83
|
} else {
|
|
69
|
-
const tempFile = `temp_concat_${uuidv4()}.mp3
|
|
70
|
-
await concatenateAudioFiles([this.currentFile, tempSilenceFile], tempFile);
|
|
71
|
-
if (isTempFile(this.currentFile)) {
|
|
84
|
+
const tempFile = path.join(this.TMP_DIR, `temp_concat_${uuidv4()}.mp3`);
|
|
85
|
+
await this.concatenateAudioFiles([this.currentFile, tempSilenceFile], tempFile);
|
|
86
|
+
if (this.isTempFile(this.currentFile)) {
|
|
72
87
|
fs.unlinkSync(this.currentFile);
|
|
73
88
|
}
|
|
74
89
|
fs.unlinkSync(tempSilenceFile); // Remove the silence file
|
|
@@ -79,9 +94,9 @@ class SfxMix {
|
|
|
79
94
|
if (this.currentFile == null) {
|
|
80
95
|
throw new Error('No audio to apply filter to. Add audio before applying filters.');
|
|
81
96
|
}
|
|
82
|
-
const tempFile = `temp_filter_${uuidv4()}.mp3
|
|
83
|
-
await applyFilter(this.currentFile, action.filterName, action.options, tempFile);
|
|
84
|
-
if (isTempFile(this.currentFile)) {
|
|
97
|
+
const tempFile = path.join(this.TMP_DIR, `temp_filter_${uuidv4()}.mp3`);
|
|
98
|
+
await this.applyFilter(this.currentFile, action.filterName, action.options, tempFile);
|
|
99
|
+
if (this.isTempFile(this.currentFile)) {
|
|
85
100
|
fs.unlinkSync(this.currentFile);
|
|
86
101
|
}
|
|
87
102
|
this.currentFile = tempFile;
|
|
@@ -106,234 +121,250 @@ class SfxMix {
|
|
|
106
121
|
this.currentFile = null;
|
|
107
122
|
return this;
|
|
108
123
|
}
|
|
109
|
-
}
|
|
110
124
|
|
|
111
|
-
//
|
|
125
|
+
// Move helper functions inside the class and use this.TMP_DIR
|
|
126
|
+
isTempFile(filename) {
|
|
127
|
+
return path.dirname(filename) === this.TMP_DIR && (
|
|
128
|
+
filename.includes('temp_concat_') ||
|
|
129
|
+
filename.includes('temp_mix_') ||
|
|
130
|
+
filename.includes('temp_silence_') ||
|
|
131
|
+
filename.includes('temp_filter_')
|
|
132
|
+
);
|
|
133
|
+
}
|
|
112
134
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
120
|
-
}
|
|
135
|
+
concatenateAudioFiles(inputFiles, outputFile) {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
// Use absolute paths for input files
|
|
138
|
+
const absoluteInputFiles = inputFiles.map(file => path.resolve(file));
|
|
139
|
+
const concatList = absoluteInputFiles.map(file => `file '${file}'`).join('\n');
|
|
140
|
+
const concatFile = path.join(this.TMP_DIR, `concat_${uuidv4()}.txt`);
|
|
121
141
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
try {
|
|
143
|
+
fs.writeFileSync(concatFile, concatList);
|
|
144
|
+
// console.log(`Concat file created at: ${concatFile}`);
|
|
145
|
+
// console.log(`Concat file contents:\n${concatList}`);
|
|
146
|
+
|
|
147
|
+
ffmpeg()
|
|
148
|
+
.input(concatFile)
|
|
149
|
+
.inputOptions(['-f', 'concat', '-safe', '0'])
|
|
150
|
+
.outputOptions(['-c', 'copy'])
|
|
151
|
+
.output(outputFile)
|
|
152
|
+
.on('start', (commandLine) => {
|
|
153
|
+
// console.log('Spawned FFmpeg with command: ' + commandLine);
|
|
154
|
+
})
|
|
155
|
+
.on('end', () => {
|
|
156
|
+
fs.unlinkSync(concatFile);
|
|
157
|
+
// console.log(`Concatenation complete. Output file: ${outputFile}`);
|
|
158
|
+
resolve();
|
|
159
|
+
})
|
|
160
|
+
.on('error', (err) => {
|
|
161
|
+
console.error('FFmpeg error:', err);
|
|
162
|
+
if (fs.existsSync(concatFile)) {
|
|
163
|
+
fs.unlinkSync(concatFile);
|
|
164
|
+
}
|
|
165
|
+
reject(err);
|
|
166
|
+
})
|
|
167
|
+
.run();
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error('Error in concatenateAudioFiles:', err);
|
|
139
170
|
reject(err);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
144
174
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
175
|
+
mixAudioFiles(inputFile1, inputFile2, outputFile, options = {}) {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
const durationOption = options.duration || 'longest'; // Default to 'longest'
|
|
178
|
+
ffmpeg()
|
|
179
|
+
.input(inputFile1)
|
|
180
|
+
.input(inputFile2)
|
|
181
|
+
.complexFilter([
|
|
182
|
+
{
|
|
183
|
+
filter: 'amix',
|
|
184
|
+
options: {
|
|
185
|
+
inputs: 2,
|
|
186
|
+
duration: durationOption,
|
|
187
|
+
},
|
|
157
188
|
},
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
}
|
|
189
|
+
])
|
|
190
|
+
.audioCodec('libmp3lame') // Ensure the codec is MP3
|
|
191
|
+
.format('mp3') // Ensure the format is MP3
|
|
192
|
+
.output(outputFile)
|
|
193
|
+
.on('end', () => resolve())
|
|
194
|
+
.on('error', (err) => reject(err))
|
|
195
|
+
.run();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
168
198
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
199
|
+
generateSilence(durationMs, outputFile) {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const durationSec = durationMs / 1000;
|
|
202
|
+
ffmpeg()
|
|
203
|
+
.input('anullsrc=channel_layout=stereo:sample_rate=44100')
|
|
204
|
+
.inputOptions(['-f', 'lavfi', '-t', `${durationSec}`])
|
|
205
|
+
.audioCodec('libmp3lame')
|
|
206
|
+
.format('mp3')
|
|
207
|
+
.output(outputFile)
|
|
208
|
+
.on('end', () => {
|
|
209
|
+
if (fs.existsSync(outputFile)) {
|
|
210
|
+
resolve();
|
|
211
|
+
} else {
|
|
212
|
+
reject(new Error(`Failed to generate silence file: ${outputFile}`));
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
.on('error', (err) => reject(err))
|
|
216
|
+
.run();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
189
219
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
applyFilter(inputFile, filterName, options, outputFile) {
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
const filterChain = this.getFilterChain(filterName, options);
|
|
223
|
+
if (!filterChain) {
|
|
224
|
+
return reject(new Error(`Unknown filter: ${filterName}`));
|
|
225
|
+
}
|
|
196
226
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
227
|
+
ffmpeg()
|
|
228
|
+
.input(inputFile)
|
|
229
|
+
.audioFilters(filterChain)
|
|
230
|
+
.audioCodec('libmp3lame')
|
|
231
|
+
.format('mp3')
|
|
232
|
+
.output(outputFile)
|
|
233
|
+
.on('end', () => resolve())
|
|
234
|
+
.on('error', (err) => reject(err))
|
|
235
|
+
.run();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
208
238
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
getFilterChain(filterName, options) {
|
|
240
|
+
switch (filterName) {
|
|
241
|
+
case 'normalize':
|
|
242
|
+
// Normalize audio using loudnorm filter with parameters
|
|
243
|
+
const tp = options.tp || -3;
|
|
244
|
+
const i = options.i || -20;
|
|
245
|
+
const lra = options.lra || 11;
|
|
246
|
+
return `loudnorm=I=${i}:TP=${tp}:LRA=${lra}:print_format=none`;
|
|
247
|
+
|
|
248
|
+
case 'telephone':
|
|
249
|
+
// Telephone effect with parameters
|
|
250
|
+
const lowFreq = options.lowFreq || 300;
|
|
251
|
+
const highFreq = options.highFreq || 2800;
|
|
252
|
+
return `highpass=f=${lowFreq}, lowpass=f=${highFreq}`;
|
|
253
|
+
|
|
254
|
+
case 'echo':
|
|
255
|
+
// Reverb effect using aecho filter
|
|
256
|
+
const inGain = options.inGain !== undefined ? options.inGain : 0.8;
|
|
257
|
+
const outGain = options.outGain !== undefined ? options.outGain : 0.9;
|
|
258
|
+
const delays = options.delays !== undefined ? options.delays : [1, 200, 300, 400];
|
|
259
|
+
const decays = options.decays !== undefined ? options.decays : [0.5, 0.5, 0.5, 0.5];
|
|
260
|
+
|
|
261
|
+
const delaysStr = delays.join('|');
|
|
262
|
+
const decaysStr = decays.join('|');
|
|
263
|
+
return `aecho=${inGain}:${outGain}:${delaysStr}:${decaysStr}`;
|
|
264
|
+
|
|
265
|
+
case 'highpass':
|
|
266
|
+
// High-pass filter
|
|
267
|
+
if (options.frequency) {
|
|
268
|
+
return `highpass=f=${options.frequency}`;
|
|
269
|
+
} else {
|
|
270
|
+
throw new Error('High-pass filter requires "frequency" option.');
|
|
271
|
+
}
|
|
242
272
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
273
|
+
case 'lowpass':
|
|
274
|
+
// Low-pass filter
|
|
275
|
+
if (options.frequency) {
|
|
276
|
+
return `lowpass=f=${options.frequency}`;
|
|
277
|
+
} else {
|
|
278
|
+
throw new Error('Low-pass filter requires "frequency" option.');
|
|
279
|
+
}
|
|
250
280
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
281
|
+
case 'volume':
|
|
282
|
+
// Volume adjustment
|
|
283
|
+
if (options.volume !== undefined) {
|
|
284
|
+
return `volume=${options.volume}`;
|
|
285
|
+
} else {
|
|
286
|
+
throw new Error('Volume filter requires "volume" option.');
|
|
287
|
+
}
|
|
258
288
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
289
|
+
case 'equalizer':
|
|
290
|
+
// Equalizer filter
|
|
291
|
+
if (options.frequency && options.width && options.gain !== undefined) {
|
|
292
|
+
return `equalizer=f=${options.frequency}:width_type=h:width=${options.width}:g=${options.gain}`;
|
|
293
|
+
} else {
|
|
294
|
+
throw new Error('Equalizer filter requires "frequency", "width", and "gain" options.');
|
|
295
|
+
}
|
|
266
296
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
297
|
+
case 'flanger':
|
|
298
|
+
// Flanger effect
|
|
299
|
+
// Options: delay, depth, regen, width, speed, shape, phase, interp
|
|
300
|
+
const flangerDelay = options.delay !== undefined ? options.delay : 0;
|
|
301
|
+
const depth = options.depth !== undefined ? options.depth : 2;
|
|
302
|
+
const regen = options.regen !== undefined ? options.regen : 0;
|
|
303
|
+
const width = options.width !== undefined ? options.width : 71;
|
|
304
|
+
const speed = options.speed !== undefined ? options.speed : 0.5;
|
|
305
|
+
const shape = options.shape !== undefined ? options.shape : 'sine';
|
|
306
|
+
const phase = options.phase !== undefined ? options.phase : 25;
|
|
307
|
+
const interp = options.interp !== undefined ? options.interp : 'linear';
|
|
308
|
+
return `aflanger=delay=${flangerDelay}:depth=${depth}:regen=${regen}:width=${width}:speed=${speed}:shape=${shape}:phase=${phase}:interp=${interp}`;
|
|
309
|
+
|
|
310
|
+
case 'pitch':
|
|
311
|
+
// Pitch shift effect
|
|
312
|
+
// Options: pitch (in semitones)
|
|
313
|
+
if (options.pitch !== undefined) {
|
|
314
|
+
const pitch = options.pitch;
|
|
315
|
+
return `asetrate=44100*${Math.pow(2, pitch / 12)},aresample=44100`;
|
|
316
|
+
} else {
|
|
317
|
+
throw new Error('Pitch filter requires "pitch" option.');
|
|
318
|
+
}
|
|
289
319
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
case 'tremolo':
|
|
321
|
+
// Tremolo effect
|
|
322
|
+
// Options: frequency
|
|
323
|
+
const frequency = options.frequency !== undefined ? options.frequency : 1;
|
|
324
|
+
return `tremolo=f=${frequency}`;
|
|
325
|
+
|
|
326
|
+
case 'phaser':
|
|
327
|
+
// Phaser effect
|
|
328
|
+
// Options: in_gain, out_gain, delay, decay, speed, type
|
|
329
|
+
const in_gain = options.in_gain !== undefined ? options.in_gain : 0.4;
|
|
330
|
+
const out_gain = options.out_gain !== undefined ? options.out_gain : 0.74;
|
|
331
|
+
const delay = options.delay !== undefined ? options.delay : 3;
|
|
332
|
+
const decay = options.decay !== undefined ? options.decay : 0.4;
|
|
333
|
+
const speed_ph = options.speed !== undefined ? options.speed : 0.5;
|
|
334
|
+
const type = options.type !== undefined ? options.type : 0;
|
|
335
|
+
return `aphaser=in_gain=${in_gain}:out_gain=${out_gain}:delay=${delay}:decay=${decay}:speed=${speed_ph}:type=${type}`;
|
|
336
|
+
|
|
337
|
+
case 'tempo':
|
|
338
|
+
// Change the speed without changing the tone
|
|
339
|
+
// Options: x (speed factor, between 0.5 and 2.0)
|
|
340
|
+
if (options.x !== undefined) {
|
|
341
|
+
const tempo = options.x;
|
|
342
|
+
if (tempo < 0.5 || tempo > 2.0) {
|
|
343
|
+
// If the value is outside the supported range, chain filters
|
|
344
|
+
const tempos = [];
|
|
345
|
+
let remainingTempo = tempo;
|
|
346
|
+
while (remainingTempo < 0.5 || remainingTempo > 2.0) {
|
|
347
|
+
if (remainingTempo < 0.5) {
|
|
348
|
+
tempos.push(0.5);
|
|
349
|
+
remainingTempo /= 0.5;
|
|
350
|
+
} else {
|
|
351
|
+
tempos.push(2.0);
|
|
352
|
+
remainingTempo /= 2.0;
|
|
353
|
+
}
|
|
323
354
|
}
|
|
355
|
+
tempos.push(remainingTempo);
|
|
356
|
+
const atempoFilters = tempos.map(t => `atempo=${t}`).join(',');
|
|
357
|
+
return atempoFilters;
|
|
358
|
+
} else {
|
|
359
|
+
return `atempo=${tempo}`;
|
|
324
360
|
}
|
|
325
|
-
tempos.push(remainingTempo);
|
|
326
|
-
const atempoFilters = tempos.map(t => `atempo=${t}`).join(',');
|
|
327
|
-
return atempoFilters;
|
|
328
361
|
} else {
|
|
329
|
-
|
|
362
|
+
throw new Error('The tempo filter requires the "x" option.');
|
|
330
363
|
}
|
|
331
|
-
} else {
|
|
332
|
-
throw new Error('The tempo filter requires the "x" option.');
|
|
333
|
-
}
|
|
334
364
|
|
|
335
|
-
|
|
336
|
-
|
|
365
|
+
default:
|
|
366
|
+
throw new Error(`Unknown filter: ${filterName}`);
|
|
367
|
+
}
|
|
337
368
|
}
|
|
338
369
|
}
|
|
339
370
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sfxmix",
|
|
3
3
|
"description": "🎧 SfxMix - Powerful and easy-to-use module for processing audio.",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.6",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"fluent-ffmpeg": "^2.1.3",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"volume",
|
|
36
36
|
"normalize",
|
|
37
37
|
"ffmpeg",
|
|
38
|
-
"fluent-ffmpeg"
|
|
38
|
+
"fluent-ffmpeg",
|
|
39
|
+
"clasen"
|
|
39
40
|
],
|
|
40
41
|
"author": "Martin Clasen",
|
|
41
42
|
"license": "MIT",
|