spessoplayer 0.5.0
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/COMMAND-LINE-OPTIONS.md +59 -0
- package/LICENSE +674 -0
- package/NOTICE +8 -0
- package/README.md +29 -0
- package/audioBuffer.mjs +284 -0
- package/cli.mjs +657 -0
- package/install.mjs +81 -0
- package/main.mjs +815 -0
- package/package.json +33 -0
- package/uninstall.mjs +80 -0
- package/utils.mjs +317 -0
package/cli.mjs
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2025 unixatch
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License
|
|
15
|
+
along with spessoplayer. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { join, basename, parse } from "path"
|
|
19
|
+
import { _dirname_, log } from "./utils.mjs"
|
|
20
|
+
|
|
21
|
+
const regexes = {
|
|
22
|
+
help: /^(?:--help|\/help|-h|\/h|\/\?)$/,
|
|
23
|
+
version: /^(?:--version|\/version|-V|\/V)$/,
|
|
24
|
+
uninstall: /^(?:--uninstall|\/uninstall|-u|\/u)$/,
|
|
25
|
+
|
|
26
|
+
verboseLevel: new RegExp([
|
|
27
|
+
"^(?:--verbose(?:=(?<number>\\d))*", // --verbose[=n]
|
|
28
|
+
"|\\/verbose(?:=(?<number>\\d))*", // /verbose[=n]
|
|
29
|
+
"|-v(?:=(?<number>\\d))*", // -v[=n]
|
|
30
|
+
"|\\/v(?:=(?<number>\\d))*)$" // /v[=n]
|
|
31
|
+
].join("")),
|
|
32
|
+
|
|
33
|
+
logFile: new RegExp([
|
|
34
|
+
"^(?:--log-file(?:=(?<path>\\w+))*", // --log-file[=n]
|
|
35
|
+
"|\\/log-file(?:=(?<path>\\w+))*", // /log-file[=n]
|
|
36
|
+
"|-lf(?:=(?<path>\\w+))*", // -lf[=n]
|
|
37
|
+
"|\\/lf(?:=(?<path>\\w+))*)$" // /lf[=n]
|
|
38
|
+
].join("")),
|
|
39
|
+
|
|
40
|
+
stdout: /^-$/,
|
|
41
|
+
wav: /^.*(?:\.wav|\.wave)$/,
|
|
42
|
+
wavFormat: /^(?:wav|wave)$/,
|
|
43
|
+
flac: /^.*\.flac$/,
|
|
44
|
+
mp3: /^.*\.mp3$/,
|
|
45
|
+
raw: /^.*\.(?:s16le|s32le|pcm)$/,
|
|
46
|
+
rawFormat: /^(?:s16le|s32le|pcm)$/,
|
|
47
|
+
|
|
48
|
+
reverbVolume: /^(?:--reverb-volume|\/reverb-volume|-rvb|\/rvb)$/,
|
|
49
|
+
effects: /^(?:--effects|\/effects|-e|\/e)$/,
|
|
50
|
+
|
|
51
|
+
format: /^(?:--format|\/format|-f|\/f)$/,
|
|
52
|
+
volume: /^(?:--volume|\/volume|-vol|\/vol)$/,
|
|
53
|
+
sampleRate: /^(?:--sample-rate|\/sample-rate|-r|\/r)$/,
|
|
54
|
+
|
|
55
|
+
loop: /^(?:--loop|\/loop|-l|\/l)$/,
|
|
56
|
+
loopStart: /^(?:--loop-start|\/loop-start|-ls|\/ls)$/,
|
|
57
|
+
loopEnd: /^(?:--loop-end|\/loop-end|-le|\/le)$/,
|
|
58
|
+
|
|
59
|
+
fileCheck: /^(?!-|\/)(?:\w|\W)*$/,
|
|
60
|
+
|
|
61
|
+
infinity: /^(?:Infinity|infinity)$/,
|
|
62
|
+
// HH:MM:SS.sss
|
|
63
|
+
ISOTimestamp: /[0-9]{1,2}:[0-9]{2}:[0-9]{2}(\.[0-9])*/,
|
|
64
|
+
areDecibels: /^(?:-|\+*)[\d.]+dB/,
|
|
65
|
+
decibelNumber: /^((?:-|\+*)[\d.]+)dB/,
|
|
66
|
+
isPercentage: /^[\d.]+%$/,
|
|
67
|
+
percentageNumber: /^([\d.]+)%$/
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Sets necessary variables in global object for main.mjs
|
|
71
|
+
* @param {Array} args - The process.argv to analyse
|
|
72
|
+
*/
|
|
73
|
+
const actUpOnPassedArgs = async (args) => {
|
|
74
|
+
const lastMidis = [];
|
|
75
|
+
let lastParam,
|
|
76
|
+
lastIndex,
|
|
77
|
+
lastSoundfont;
|
|
78
|
+
let newArguments = args.slice(2);
|
|
79
|
+
if (newArguments.length === 0) {
|
|
80
|
+
help()
|
|
81
|
+
process.exit()
|
|
82
|
+
}
|
|
83
|
+
if (newArguments.filter(i => regexes.help.test(i)).length > 0) {
|
|
84
|
+
help()
|
|
85
|
+
process.exit()
|
|
86
|
+
}
|
|
87
|
+
if (newArguments.filter(i => regexes.version.test(i)).length > 0) {
|
|
88
|
+
await version()
|
|
89
|
+
process.exit()
|
|
90
|
+
}
|
|
91
|
+
if (newArguments.filter(i => regexes.uninstall.test(i)).length > 0) {
|
|
92
|
+
await uninstall()
|
|
93
|
+
process.exit()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const isVerboseLevelSet = newArguments.find(i => regexes.verboseLevel.test(i));
|
|
97
|
+
if (isVerboseLevelSet) {
|
|
98
|
+
let verboseOptionNumber = isVerboseLevelSet.match(regexes.verboseLevel).groups.number;
|
|
99
|
+
const verboseOptionPosition = newArguments.indexOf(isVerboseLevelSet);
|
|
100
|
+
|
|
101
|
+
if (!verboseOptionNumber) verboseOptionNumber = "1";
|
|
102
|
+
// Delete verbose-level from newArguments
|
|
103
|
+
newArguments.splice(verboseOptionPosition, 1)
|
|
104
|
+
|
|
105
|
+
if (!process.env["DEBUG_LEVEL_SPESSO"]) {
|
|
106
|
+
await setVerboseLevel(verboseOptionNumber)
|
|
107
|
+
} else log(1, performance.now().toFixed(2), `Using variable DEBUG_LEVEL_SPESSO=${process.env["DEBUG_LEVEL_SPESSO"]}`)
|
|
108
|
+
} else if (process.env["DEBUG_LEVEL_SPESSO"]) {
|
|
109
|
+
log(1, performance.now().toFixed(2), `Using variable DEBUG_LEVEL_SPESSO=${process.env["DEBUG_LEVEL_SPESSO"]}`)
|
|
110
|
+
}
|
|
111
|
+
const isPathOfLogFileSet = newArguments.find(i => regexes.logFile.test(i));
|
|
112
|
+
if (isPathOfLogFileSet
|
|
113
|
+
&& !isVerboseLevelSet
|
|
114
|
+
&& !process.env["DEBUG_LEVEL_SPESSO"]) {
|
|
115
|
+
await setVerboseLevel("1")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isPathOfLogFileSet) {
|
|
119
|
+
const pathOfLogFile = isPathOfLogFileSet.match(regexes.logFile).groups.path;
|
|
120
|
+
const pathOfLogFilePosition = newArguments.indexOf(isPathOfLogFileSet);
|
|
121
|
+
|
|
122
|
+
// Delete verbose-level from newArguments
|
|
123
|
+
newArguments.splice(pathOfLogFilePosition, 1)
|
|
124
|
+
|
|
125
|
+
if (!process.env["DEBUG_FILE_SPESSO"]) {
|
|
126
|
+
setLogFilePath(pathOfLogFile)
|
|
127
|
+
} else log(1, performance.now().toFixed(2), `Using variable DEBUG_FILE_SPESSO=${process.env["DEBUG_FILE_SPESSO"]}`)
|
|
128
|
+
} else if (process.env["DEBUG_FILE_SPESSO"]) {
|
|
129
|
+
log(1, performance.now().toFixed(2), `Using variable DEBUG_FILE_SPESSO=${process.env["DEBUG_FILE_SPESSO"]}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
global.fileOutputs = [];
|
|
133
|
+
for (const arg of newArguments) {
|
|
134
|
+
switch (arg) {
|
|
135
|
+
case regexes.wav.test(arg) && arg: {
|
|
136
|
+
global.fileOutputs[0] = arg;
|
|
137
|
+
log(1, performance.now().toFixed(2), "Set file output to wav")
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case regexes.flac.test(arg) && arg: {
|
|
141
|
+
global.fileOutputs[1] = arg;
|
|
142
|
+
log(1, performance.now().toFixed(2), "Set file output to flac")
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case regexes.mp3.test(arg) && arg: {
|
|
146
|
+
global.fileOutputs[2] = arg;
|
|
147
|
+
log(1, performance.now().toFixed(2), "Set file output to mp3")
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case regexes.raw.test(arg) && arg: {
|
|
151
|
+
global.fileOutputs[3] = arg;
|
|
152
|
+
log(1, performance.now().toFixed(2), "Set file output to pcm")
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case regexes.stdout.test(arg) && arg: {
|
|
156
|
+
global.toStdout = true;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case regexes.reverbVolume.test(arg) && arg: {
|
|
160
|
+
// In case there's no other argument
|
|
161
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
162
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
163
|
+
|
|
164
|
+
lastParam = "reverb"
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case regexes.volume.test(arg) && arg: {
|
|
168
|
+
// In case there's no other argument
|
|
169
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
170
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
171
|
+
|
|
172
|
+
lastParam = "volume"
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case regexes.effects.test(arg) && arg: {
|
|
176
|
+
// In case there's no other argument
|
|
177
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
178
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
179
|
+
|
|
180
|
+
lastParam = "effects"
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case regexes.format.test(arg) && arg: {
|
|
184
|
+
// In case there's no other argument
|
|
185
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
186
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
187
|
+
|
|
188
|
+
lastParam = "format"
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case regexes.sampleRate.test(arg) && arg: {
|
|
192
|
+
// In case there's no other argument
|
|
193
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
194
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
195
|
+
|
|
196
|
+
lastParam = "sample-rate"
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case regexes.loopStart.test(arg) && arg: {
|
|
200
|
+
// In case there's no other argument
|
|
201
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
202
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
203
|
+
|
|
204
|
+
lastParam = "loop-start"
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case regexes.loopEnd.test(arg) && arg: {
|
|
208
|
+
// In case there's no other argument
|
|
209
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
210
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
211
|
+
|
|
212
|
+
lastParam = "loop-end"
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case regexes.loop.test(arg) && arg: {
|
|
216
|
+
// In case there's no other argument
|
|
217
|
+
const indexOfArg = newArguments.indexOf(arg);
|
|
218
|
+
if (newArguments[indexOfArg + 1] === undefined) throw new ReferenceError("Missing necessary argument");
|
|
219
|
+
|
|
220
|
+
lastParam = "loop"
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case regexes.fileCheck.test(basename(arg)) && arg: {
|
|
224
|
+
if (lastParam === undefined) {
|
|
225
|
+
const fs = await import("node:fs");
|
|
226
|
+
global["fs"] = fs;
|
|
227
|
+
let fileMagicNumber;
|
|
228
|
+
await new Promise((resolve, reject) => {
|
|
229
|
+
const readStream = fs.createReadStream(arg, { start: 0, end: 20 });
|
|
230
|
+
readStream.on("data", (data) => {
|
|
231
|
+
fileMagicNumber = data.toString();
|
|
232
|
+
resolve()
|
|
233
|
+
})
|
|
234
|
+
readStream.on("error", (e) => {
|
|
235
|
+
if (e.code === "ENOENT") console.error(`${red}Can't open '${arg}' because it doesn't exist${normal}`)
|
|
236
|
+
process.exit(1)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
// MIDI files
|
|
240
|
+
if (fileMagicNumber.includes("MThd")) {
|
|
241
|
+
global.midiFile = arg;
|
|
242
|
+
log(1, performance.now().toFixed(2), `Set midi file to "${global.midiFile}"`)
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
if (fileMagicNumber.includes("sfbk")) {
|
|
246
|
+
// Soundfont files
|
|
247
|
+
global.soundfontFile = arg;
|
|
248
|
+
log(1, performance.now().toFixed(2), `Set soundfont file to "${global.soundfontFile}"`)
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (fileMagicNumber.includes("DLS")) {
|
|
252
|
+
// Downloadable sounds files
|
|
253
|
+
global.soundfontFile = arg;
|
|
254
|
+
log(1, performance.now().toFixed(2), `Set downloadable sounds file to "${global.soundfontFile}"`)
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
default:
|
|
261
|
+
switch (lastParam) {
|
|
262
|
+
case "loop":
|
|
263
|
+
setLoop(arg)
|
|
264
|
+
lastParam = undefined;
|
|
265
|
+
break;
|
|
266
|
+
case "loop-start":
|
|
267
|
+
setLoopStart(arg)
|
|
268
|
+
lastParam = undefined;
|
|
269
|
+
break;
|
|
270
|
+
case "loop-end":
|
|
271
|
+
setLoopEnd(arg)
|
|
272
|
+
lastParam = undefined;
|
|
273
|
+
break;
|
|
274
|
+
case "sample-rate":
|
|
275
|
+
setSampleRate(arg)
|
|
276
|
+
lastParam = undefined;
|
|
277
|
+
break;
|
|
278
|
+
case "format":
|
|
279
|
+
setFormat(arg)
|
|
280
|
+
lastParam = undefined;
|
|
281
|
+
break;
|
|
282
|
+
case "volume":
|
|
283
|
+
setVolume(arg)
|
|
284
|
+
lastParam = undefined;
|
|
285
|
+
break;
|
|
286
|
+
case "reverb":
|
|
287
|
+
setReverb(arg)
|
|
288
|
+
lastParam = undefined;
|
|
289
|
+
break;
|
|
290
|
+
case "effects":
|
|
291
|
+
setEffects(arg)
|
|
292
|
+
lastParam = undefined;
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
default:
|
|
296
|
+
// Invalid param
|
|
297
|
+
console.error(red+`'${
|
|
298
|
+
underline+dimRed +
|
|
299
|
+
arg +
|
|
300
|
+
normal+red
|
|
301
|
+
}' is an invalid parameter`+normal)
|
|
302
|
+
help()
|
|
303
|
+
process.exit()
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (global?.midiFile === undefined) {
|
|
308
|
+
console.error(`${red}Missing a required midi file${normal}`);
|
|
309
|
+
process.exit(1)
|
|
310
|
+
}
|
|
311
|
+
if (global?.soundfontFile === undefined) {
|
|
312
|
+
console.error(`${red}Missing a required soundfont file${normal}`);
|
|
313
|
+
process.exit(1)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Sets the global.loopN variable
|
|
319
|
+
* @param {String} arg - the loop amount
|
|
320
|
+
*/
|
|
321
|
+
const setLoop = arg => {
|
|
322
|
+
if (typeof Number(arg) === "number"
|
|
323
|
+
&& !regexes.infinity.test(arg)) {
|
|
324
|
+
global.loopN = Number(arg);
|
|
325
|
+
log(1, performance.now().toFixed(2), `Set loop amount to ${global.loopN}`)
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (regexes.infinity.test(arg)) {
|
|
329
|
+
console.error(`${normalRed}Can't use infinity, sorry${normal}`)
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
console.error(`${normalRed}Passed something that wasn't a number${normal}`)
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Sets the global.loopStart variable
|
|
337
|
+
* @param {String} arg - the start of the loop in seconds or in HH:MM:SS:ms format
|
|
338
|
+
*/
|
|
339
|
+
const setLoopStart = arg => {
|
|
340
|
+
if (typeof Number(arg) === "number"
|
|
341
|
+
|| !Number.isNaN(Date.parse(`1970T${arg}Z`))) {
|
|
342
|
+
if (regexes.ISOTimestamp.test(arg)) {
|
|
343
|
+
const seconds = Date.parse(`1970T${arg}Z`) / 1000;
|
|
344
|
+
global.loopStart = seconds;
|
|
345
|
+
log(1, performance.now().toFixed(2), `Set loop-start to ${global.loopStart}`)
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
log(1, performance.now().toFixed(2), `Set loop-start to ${global.loopStart}`)
|
|
349
|
+
global.loopStart = Number(arg);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
console.error(`${normalRed}Passed something that wasn't a number or in ISO string format${normal}`)
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Sets the global.loopEnd variable
|
|
357
|
+
* @param {String} arg - the end of the loop in seconds or in HH:MM:SS:ms format
|
|
358
|
+
*/
|
|
359
|
+
const setLoopEnd = arg => {
|
|
360
|
+
if (typeof Number(arg) === "number"
|
|
361
|
+
|| !Number.isNaN(Date.parse(`1970T${arg}Z`))) {
|
|
362
|
+
if (regexes.ISOTimestamp.test(arg)) {
|
|
363
|
+
const seconds = Date.parse(`1970T${arg}Z`) / 1000;
|
|
364
|
+
global.loopEnd = seconds;
|
|
365
|
+
log(1, performance.now().toFixed(2), `Set loop-end to ${global.loopEnd}`)
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
log(1, performance.now().toFixed(2), `Set loop-end to ${global.loopEnd}`)
|
|
369
|
+
global.loopEnd = Number(arg);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
console.error(`${normalRed}Passed something that wasn't a number or in ISO string format${normal}`)
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Sets the global.sampleRate variable
|
|
377
|
+
* @param {String} arg - the sample rate to set
|
|
378
|
+
*/
|
|
379
|
+
const setSampleRate = arg => {
|
|
380
|
+
if (typeof Number(arg) === "number" && !arg.startsWith("-")) {
|
|
381
|
+
log(1, performance.now().toFixed(2), `Set sample rate to ${global.sampleRate}`)
|
|
382
|
+
global.sampleRate = Number(arg);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
console.error(`${normalRed}Passed something that wasn't a valid number${normal}`)
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Simply changes how the program should log
|
|
390
|
+
* @param {Number} arg - the level of how much it should log
|
|
391
|
+
*/
|
|
392
|
+
const setVerboseLevel = async (arg) => {
|
|
393
|
+
const isFromUser = arg !== undefined;
|
|
394
|
+
if (!arg) arg = "2";
|
|
395
|
+
if (!global.fs) global.fs = await import("fs");
|
|
396
|
+
if (typeof Number(arg) === "number"
|
|
397
|
+
&& !(Number(arg) < 0 && Number(arg) > 2)
|
|
398
|
+
&& !arg.startsWith("-")) {
|
|
399
|
+
global.verboseLevel = Number(arg);
|
|
400
|
+
if (isFromUser) {
|
|
401
|
+
log(1, performance.now().toFixed(2), `Set verbose level asked by the user to ${global.verboseLevel}`)
|
|
402
|
+
} else log(1, performance.now().toFixed(2), `Set verbose level to ${global.verboseLevel}`)
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
console.error(`${normalRed}Passed something that wasn't a valid number${normal}`)
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Sets the global.format variable for use in stdout mode
|
|
410
|
+
* @param {String} arg - the format to use (similar to ffmpeg's -f)
|
|
411
|
+
*/
|
|
412
|
+
const setFormat = arg => {
|
|
413
|
+
switch (arg) {
|
|
414
|
+
case regexes.wavFormat.test(arg) && arg: {
|
|
415
|
+
global.format = "wave";
|
|
416
|
+
log(1, performance.now().toFixed(2), `Set stdout format to ${global.format}`)
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
case "flac": {
|
|
420
|
+
global.format = "flac";
|
|
421
|
+
log(1, performance.now().toFixed(2), `Set stdout format to ${global.format}`)
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
case "mp3": {
|
|
425
|
+
global.format = "mp3";
|
|
426
|
+
log(1, performance.now().toFixed(2), `Set stdout format to ${global.format}`)
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
case regexes.rawFormat.test(arg) && arg: {
|
|
430
|
+
global.format = "pcm";
|
|
431
|
+
log(1, performance.now().toFixed(2), `Set stdout format to ${global.format}`)
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
console.error(`${normalRed}Passed something that wasn't an available format${normal}`)
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Applies effects from the user's string passed through --effects
|
|
440
|
+
* @param {String} arg - the comma-separeted string to parse
|
|
441
|
+
*/
|
|
442
|
+
const setEffects = arg => {
|
|
443
|
+
const regexListOfEffects =
|
|
444
|
+
"allpass|band|bandpass|bandreject|bass|bend|biquad" +
|
|
445
|
+
"|chorus|channels|compand|contrast|dcshift|deemph|delay" +
|
|
446
|
+
"|dither|divide|downsample|earwax|echo|echos|equalizer" +
|
|
447
|
+
"|fade|fir|firfit|flanger|gain|highpass|hilbert|input" +
|
|
448
|
+
"|ladspa|loudness|lowpass|mcompand|noiseprof|noisered" +
|
|
449
|
+
"|norm|oops|output|overdrive|pad|phaser|pitch|rate|remix" +
|
|
450
|
+
"|repeat|reverb|reverse|riaa|silence|sinc|spectrogram" +
|
|
451
|
+
"|speed|splice|stat|stats|stretch|swap|synth|tempo" +
|
|
452
|
+
"|treble|tremolo|trim|upsample|vad|vol";
|
|
453
|
+
const regexGroupListGetter = /([a-z]+) ?([-a-z\d ]+)?/gm;
|
|
454
|
+
const regexTests = {
|
|
455
|
+
// is it a list structured like
|
|
456
|
+
// <effect1>[values1],<effect2>[values2]?
|
|
457
|
+
normalList: new RegExp(`${regexGroupListGetter.source},${regexGroupListGetter.source}`).test(arg),
|
|
458
|
+
// is it a single effect like
|
|
459
|
+
// <effect>[values]?
|
|
460
|
+
isIncorrect: new RegExp(`^${regexGroupListGetter.source}[^,](?:${regexListOfEffects}).*$`).test(arg)
|
|
461
|
+
}
|
|
462
|
+
if (regexTests.normalList || !regexTests.isIncorrect) {
|
|
463
|
+
const list = [
|
|
464
|
+
...arg
|
|
465
|
+
.matchAll(regexGroupListGetter)
|
|
466
|
+
.map(i => ({
|
|
467
|
+
effect: i[1],
|
|
468
|
+
values: (i[2])
|
|
469
|
+
? i[2].split(
|
|
470
|
+
(i[2].includes(",")) ? "," : " "
|
|
471
|
+
)
|
|
472
|
+
: undefined
|
|
473
|
+
}) )
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
if (!list
|
|
477
|
+
.every(i => new RegExp(regexListOfEffects).test(i.effect))
|
|
478
|
+
) {
|
|
479
|
+
console.error(`${normalRed}One effect that you passed doesn't exist in SoX${normal}`);
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
global.effects = list;
|
|
484
|
+
log(1, performance.now().toFixed(2), `Set list of SoX effects as ${global.effects}`)
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
console.error(`${normalRed}The string for SoX effects you passed is not usable${normal}`);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Sets the global.volume variable for the masterGain
|
|
492
|
+
* @param {String} arg - the volume in either percentage, decibels or decimals
|
|
493
|
+
*/
|
|
494
|
+
const setVolume = arg => {
|
|
495
|
+
if (regexes.areDecibels.test(arg)) {
|
|
496
|
+
const dBNumber = Number(arg.match(regexes.decibelNumber)[1]);
|
|
497
|
+
const toPercentage = 10**(dBNumber/10);
|
|
498
|
+
global.volume = toPercentage;
|
|
499
|
+
log(1, performance.now().toFixed(2), `Set volume to ${global.volume}`)
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (regexes.isPercentage.test(arg)) {
|
|
503
|
+
const percentage = Number(arg.match(regexes.percentageNumber)[1]);
|
|
504
|
+
global.volume = percentage / 100;
|
|
505
|
+
log(1, performance.now().toFixed(2), `Set volume to ${global.volume}`)
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (typeof Number(arg) === "number" && !arg.startsWith("-")) {
|
|
509
|
+
global.volume = Number(arg);
|
|
510
|
+
log(1, performance.now().toFixed(2), `Set volume to ${global.volume}`)
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
console.error(`${normalRed}Passed something that wasn't a valid number/dB/percentage${normal}`)
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Sets the global.reverb variable
|
|
518
|
+
* @param {String} arg - the volume in either percentage, decibels or decimals
|
|
519
|
+
*/
|
|
520
|
+
const setReverb = arg => {
|
|
521
|
+
if (regexes.areDecibels.test(arg)) {
|
|
522
|
+
const dBNumber = Number(arg.match(regexes.decibelNumber)[1]);
|
|
523
|
+
global.reverbVolume = dBNumber;
|
|
524
|
+
global.effects = true;
|
|
525
|
+
log(1, performance.now().toFixed(2), `Set reverb volume to ${global.reverbVolume} and effects variable to ${global.effects}`)
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (regexes.isPercentage.test(arg)) {
|
|
529
|
+
const percentage = Number(arg.match(regexes.percentageNumber)[1]);
|
|
530
|
+
const toDB = 10 * 10**(percentage/100);
|
|
531
|
+
global.reverbVolume = toDB;
|
|
532
|
+
global.effects = true;
|
|
533
|
+
log(1, performance.now().toFixed(2), `Set reverb volume to ${global.reverbVolume} and effects variable to ${global.effects}`)
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (typeof Number(arg) === "number" && !arg.startsWith("-")) {
|
|
537
|
+
global.reverbVolume = Number(arg);
|
|
538
|
+
global.effects = true;
|
|
539
|
+
log(1, performance.now().toFixed(2), `Set reverb volume to ${global.reverbVolume} and effects variable to ${global.effects}`)
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
console.error(`${normalRed}Passed something that wasn't a valid number/dB/percentage${normal}`)
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Sets the file path to the log file
|
|
547
|
+
* @param {String} arg - Path to the log file
|
|
548
|
+
*/
|
|
549
|
+
const setLogFilePath = arg => {
|
|
550
|
+
global.logFilePath = arg ?? "./spesso.log";
|
|
551
|
+
log(1, performance.now().toFixed(2), `Set log file path to ${global.logFilePath}`)
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Runs uninstall.mjs and uninstall spessoplayer
|
|
555
|
+
*/
|
|
556
|
+
const uninstall = async () => {
|
|
557
|
+
const { execSync } = await import("child_process");
|
|
558
|
+
const uninstallScriptPath = join(_dirname_, "uninstall.mjs");
|
|
559
|
+
const isGloballyInstalled = /spessoplayer/.test(execSync("npm ls -g").toString());
|
|
560
|
+
|
|
561
|
+
log(1, performance.now().toFixed(2), `Launched ${uninstallScriptPath}`)
|
|
562
|
+
try {
|
|
563
|
+
execSync(`node ${uninstallScriptPath}`, {stdio: "inherit"})
|
|
564
|
+
} catch (e) {
|
|
565
|
+
if (e.status !== 0 && e.status !== 2) {
|
|
566
|
+
console.error(`${red}Uninstallation interrupted with error ${e.status}${normal}`);
|
|
567
|
+
process.exit(2);
|
|
568
|
+
}
|
|
569
|
+
if (e.status === 2) process.exit(2)
|
|
570
|
+
}
|
|
571
|
+
log(1, performance.now().toFixed(2), "Uninstalling spessoplayer")
|
|
572
|
+
execSync(`npm uninstall ${(isGloballyInstalled) ? "-g" : ""} spessoplayer`, { cwd: ".", stdio: "inherit" })
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Shows the help text
|
|
576
|
+
*/
|
|
577
|
+
const help = () => {
|
|
578
|
+
const helpText = `${underline}spessoplayer${normal}
|
|
579
|
+
${dimGrayBold}A midi converter that uses spessasynth_core to generate the data${normal}
|
|
580
|
+
|
|
581
|
+
Usage:
|
|
582
|
+
${bold}spessoplayer${normal} [${dimGray}options${normal}] <midi> <soundfont> [${dimGray}outFile${normal}]
|
|
583
|
+
|
|
584
|
+
Available parameters:
|
|
585
|
+
${green}--volume${normal}, ${green}/volume${normal}, ${green}-vol${normal}, ${green}/vol${normal}:
|
|
586
|
+
${dimGray+italics}Volume to set (default: 100%)${normal}
|
|
587
|
+
|
|
588
|
+
${dimGray+italics}Available formats:${normal}
|
|
589
|
+
${dimGray+italics}- dB (example -10dB)${normal}
|
|
590
|
+
${dimGray+italics}- percentages (example 70%)${normal}
|
|
591
|
+
${dimGray+italics}- decimals (example 0.9)${normal}
|
|
592
|
+
|
|
593
|
+
${green}--reverb-volume${normal}, ${green}/reverb-volume${normal}, ${green}-rvb${normal}, ${green}/rvb${normal}:
|
|
594
|
+
${dimGray+italics}Volume to set for reverb (default: none)${normal}
|
|
595
|
+
${dimGray+italics}Same formats as volume${normal}
|
|
596
|
+
|
|
597
|
+
${green}--effects${normal}, ${green}/effects${normal}, ${green}-e${normal}, ${green}/e${normal}:
|
|
598
|
+
${dimGray+italics}Adds any effects that SoX provides (e.g "reverb,fade 1")${normal}
|
|
599
|
+
|
|
600
|
+
${green}--loop${normal}, ${green}/loop${normal}, ${green}-l${normal}, ${green}/l${normal}:
|
|
601
|
+
${dimGray+italics}Loop x amount of times (default: 0)${normal}
|
|
602
|
+
${dimGray+italics}(It might be slow with bigger numbers)${normal}
|
|
603
|
+
|
|
604
|
+
${green}--loop-start${normal}, ${green}/loop-start${normal}, ${green}-ls${normal}, ${green}/ls${normal}:
|
|
605
|
+
${dimGray+italics}When the loop starts${normal}
|
|
606
|
+
|
|
607
|
+
${green}--loop-end${normal}, ${green}/loop-end${normal}, ${green}-le${normal}, ${green}/le${normal}:
|
|
608
|
+
${dimGray+italics}When the loop ends${normal}
|
|
609
|
+
|
|
610
|
+
${green}--sample-rate${normal}, ${green}/sample-rate${normal}, ${green}-r${normal}, ${green}/r${normal}:
|
|
611
|
+
${dimGray+italics}Sample rate to use (default: 48000)${normal}
|
|
612
|
+
${dimGray+italics}(It might be slow with bigger numbers for players like mpv)${normal}
|
|
613
|
+
${dimGray+italics}(Some players might downsize it to a smaller frequency)${normal}
|
|
614
|
+
|
|
615
|
+
${green}--format${normal}, ${green}/format${normal}, ${green}-f${normal}, ${green}/f${normal}:
|
|
616
|
+
${dimGray+italics}Format to use for stdout (default: wav)${normal}
|
|
617
|
+
|
|
618
|
+
${dimGray+italics}Available formats:${normal}
|
|
619
|
+
${dimGray+italics}- wav${normal}
|
|
620
|
+
${dimGray+italics}- mp3${normal}
|
|
621
|
+
${dimGray+italics}- flac${normal}
|
|
622
|
+
${dimGray+italics}- pcm (s32le)${normal}
|
|
623
|
+
|
|
624
|
+
${green}--verbose${normal}, ${green}/verbose${normal}, ${green}-v${normal}, ${green}/v${normal}:
|
|
625
|
+
${dimGray+italics}Sets the verbosity (default: 2)${normal}
|
|
626
|
+
|
|
627
|
+
${green}--log-file${normal}, ${green}/log-file${normal}, ${green}-lf${normal}, ${green}/lf${normal}:
|
|
628
|
+
${dimGray+italics}Sets path to the log file (default: ./spesso.log)${normal}
|
|
629
|
+
${dimGray+italics}(Meanwhile it writes to file, it also prints to stderr)${normal}
|
|
630
|
+
|
|
631
|
+
${green}--uninstall${normal}, ${green}/uninstall${normal}, ${green}-u${normal}, ${green}/u${normal}:
|
|
632
|
+
${dimGray+italics}Uninstalls dependencies with confirmation and the entire program${normal}
|
|
633
|
+
|
|
634
|
+
${green}--help${normal}, ${green}/help${normal}, ${green}-h${normal}, ${green}/h${normal}, ${green}/?${normal}:
|
|
635
|
+
${dimGray+italics}Shows this help message${normal}
|
|
636
|
+
|
|
637
|
+
${green}--version${normal}, ${green}/version${normal}:
|
|
638
|
+
${dimGray+italics}Shows the installed version${normal}
|
|
639
|
+
`
|
|
640
|
+
console.log(helpText)
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Shows the version number taken from package.json
|
|
644
|
+
*/
|
|
645
|
+
const version = async () => {
|
|
646
|
+
const fs = await import("node:fs");
|
|
647
|
+
const packageJSONPath = join(_dirname_, "package.json");
|
|
648
|
+
const { versionNumber } = JSON.parse(fs.readFileSync(packageJSONPath).toString());
|
|
649
|
+
|
|
650
|
+
log(1, performance.now().toFixed(2), `Taken version number from ${packageJSONPath}`)
|
|
651
|
+
console.log(`${green + versionNumber + normal}`)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export {
|
|
655
|
+
actUpOnPassedArgs,
|
|
656
|
+
join, parse
|
|
657
|
+
}
|