liquidsoap-prettier 1.8.2 → 1.8.3
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/.github/workflows/check-formatting.yml +32 -0
- package/README.md +31 -5
- package/package.json +1 -1
- package/src/cli.js +104 -9
- package/tests/liq/audio.liq +460 -0
- package/tests/liq/autocue.liq +1081 -0
- package/tests/liq/clock.liq +14 -0
- package/tests/liq/cron.liq +74 -0
- package/tests/liq/error.liq +48 -0
- package/tests/liq/extra/audio.liq +677 -0
- package/tests/liq/extra/audioscrobbler.liq +482 -0
- package/tests/liq/extra/deprecations.liq +976 -0
- package/tests/liq/extra/externals.liq +196 -0
- package/tests/liq/extra/fades.liq +260 -0
- package/tests/liq/extra/file.liq +66 -0
- package/tests/liq/extra/http.liq +160 -0
- package/tests/liq/extra/interactive.liq +917 -0
- package/tests/liq/extra/metadata.liq +75 -0
- package/tests/liq/extra/native.liq +201 -0
- package/tests/liq/extra/openai.liq +150 -0
- package/tests/liq/extra/server.liq +177 -0
- package/tests/liq/extra/source.liq +476 -0
- package/tests/liq/extra/spinitron.liq +272 -0
- package/tests/liq/extra/telnet.liq +266 -0
- package/tests/liq/extra/video.liq +59 -0
- package/tests/liq/extra/visualization.liq +68 -0
- package/tests/liq/fades.liq +941 -0
- package/tests/liq/ffmpeg.liq +605 -0
- package/tests/liq/file.liq +387 -0
- package/tests/liq/getter.liq +74 -0
- package/tests/liq/hls.liq +329 -0
- package/tests/liq/http.liq +1048 -0
- package/tests/liq/http_codes.liq +447 -0
- package/tests/liq/icecast.liq +58 -0
- package/tests/liq/io.liq +106 -0
- package/tests/liq/liquidsoap.liq +31 -0
- package/tests/liq/list.liq +440 -0
- package/tests/liq/log.liq +47 -0
- package/tests/liq/lufs.liq +295 -0
- package/tests/liq/math.liq +23 -0
- package/tests/liq/medialib.liq +752 -0
- package/tests/liq/metadata.liq +253 -0
- package/tests/liq/nfo.liq +258 -0
- package/tests/liq/null.liq +71 -0
- package/tests/liq/playlist.liq +1347 -0
- package/tests/liq/predicate.liq +106 -0
- package/tests/liq/process.liq +93 -0
- package/tests/liq/profiler.liq +5 -0
- package/tests/liq/protocols.liq +1139 -0
- package/tests/liq/ref.liq +28 -0
- package/tests/liq/replaygain.liq +135 -0
- package/tests/liq/request.liq +467 -0
- package/tests/liq/resolvers.liq +33 -0
- package/tests/liq/runtime.liq +70 -0
- package/tests/liq/server.liq +99 -0
- package/tests/liq/settings.liq +41 -0
- package/tests/liq/socket.liq +33 -0
- package/tests/liq/source.liq +362 -0
- package/tests/liq/sqlite.liq +161 -0
- package/tests/liq/stdlib.liq +172 -0
- package/tests/liq/string.liq +476 -0
- package/tests/liq/switches.liq +197 -0
- package/tests/liq/testing.liq +37 -0
- package/tests/liq/thread.liq +161 -0
- package/tests/liq/tracks.liq +100 -0
- package/tests/liq/utils.liq +81 -0
- package/tests/liq/video.liq +918 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Check formatting
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
merge_group:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
check:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: lts/*
|
|
23
|
+
|
|
24
|
+
- uses: pnpm/action-setup@v4
|
|
25
|
+
with:
|
|
26
|
+
version: 10
|
|
27
|
+
run_install: true
|
|
28
|
+
|
|
29
|
+
- run: pnpm dev:prepare
|
|
30
|
+
|
|
31
|
+
- name: Check that stdlib files are formatted
|
|
32
|
+
run: node src/cli.js --check 'tests/liq/**/*.liq'
|
package/README.md
CHANGED
|
@@ -11,18 +11,44 @@ The `liquidsoap-prettier` command-line utility should be installed with this
|
|
|
11
11
|
package and should be available following the usual node package binary
|
|
12
12
|
conventions.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Format one or more files in place:
|
|
15
15
|
|
|
16
16
|
```shell
|
|
17
|
-
$ liquidsoap-prettier
|
|
17
|
+
$ liquidsoap-prettier -w path/to/file.liq "path/with/glob/pattern/**/*.liq"
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Print formatted output to stdout (single file only):
|
|
21
|
+
|
|
22
|
+
```shell
|
|
23
|
+
$ liquidsoap-prettier path/to/file.liq
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Read from stdin and write to stdout:
|
|
27
|
+
|
|
28
|
+
```shell
|
|
29
|
+
$ cat path/to/file.liq | liquidsoap-prettier --stdin-filepath file.liq
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Check whether files are already formatted (useful in CI):
|
|
33
|
+
|
|
21
34
|
```shell
|
|
22
|
-
$ liquidsoap-prettier
|
|
35
|
+
$ liquidsoap-prettier -c path/to/file.liq "path/with/glob/pattern/**/*.liq"
|
|
23
36
|
```
|
|
24
37
|
|
|
25
|
-
|
|
38
|
+
Returns exit code `0` when all files are already formatted, `2` otherwise.
|
|
39
|
+
|
|
40
|
+
Dump the parsed AST as JSON (useful when debugging the printer):
|
|
41
|
+
|
|
42
|
+
```shell
|
|
43
|
+
$ liquidsoap-prettier --dump-ast path/to/file.liq
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Print the version or show all options:
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
$ liquidsoap-prettier --version
|
|
50
|
+
$ liquidsoap-prettier --help
|
|
51
|
+
```
|
|
26
52
|
|
|
27
53
|
### Prettier plugin
|
|
28
54
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -5,38 +5,130 @@ import prettier from "prettier";
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import { glob } from "glob";
|
|
7
7
|
import BluebirdPromise from "bluebird";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
8
9
|
import * as prettierPluginLiquidsoap from "./index.js";
|
|
9
10
|
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const { version } = require("../package.json");
|
|
13
|
+
|
|
14
|
+
const HELP = `\
|
|
15
|
+
Usage: liquidsoap-prettier [options] <file> [<file> ...]
|
|
16
|
+
liquidsoap-prettier [options] --stdin-filepath <name>
|
|
17
|
+
|
|
18
|
+
Format Liquidsoap (.liq) files.
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
<file> One or more files or glob patterns to format.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
-w, --write Write formatted output back to each file in place.
|
|
25
|
+
-c, --check Check if files are already formatted (exit 2 if not).
|
|
26
|
+
--print-width Maximum line width (default: 80).
|
|
27
|
+
--stdin-filepath Read from stdin; use <name> in error messages.
|
|
28
|
+
--dump-ast Parse and dump the AST as JSON (for debugging).
|
|
29
|
+
-v, --version Print the version and exit.
|
|
30
|
+
-h, --help Show this help message and exit.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
liquidsoap-prettier script.liq # print formatted output to stdout
|
|
34
|
+
liquidsoap-prettier -w script.liq # format file in place
|
|
35
|
+
liquidsoap-prettier -w '**/*.liq' # format all .liq files in place
|
|
36
|
+
liquidsoap-prettier -c '**/*.liq' # check formatting of all .liq files
|
|
37
|
+
liquidsoap-prettier -c script.liq # check formatting (CI mode)
|
|
38
|
+
cat script.liq | liquidsoap-prettier --stdin-filepath script.liq
|
|
39
|
+
liquidsoap-prettier --dump-ast script.liq # dump parsed AST as JSON
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const readStdin = () =>
|
|
43
|
+
new Promise((resolve, reject) => {
|
|
44
|
+
let data = "";
|
|
45
|
+
process.stdin.setEncoding("utf8");
|
|
46
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
47
|
+
process.stdin.on("end", () => resolve(data));
|
|
48
|
+
process.stdin.on("error", reject);
|
|
49
|
+
});
|
|
50
|
+
|
|
10
51
|
const run = async () => {
|
|
11
52
|
try {
|
|
12
53
|
const {
|
|
13
|
-
_:
|
|
54
|
+
_: patterns,
|
|
14
55
|
write,
|
|
15
56
|
w,
|
|
16
57
|
check,
|
|
17
58
|
c,
|
|
59
|
+
version: showVersion,
|
|
60
|
+
v,
|
|
61
|
+
help,
|
|
62
|
+
h,
|
|
18
63
|
"print-width": printWidth = 80,
|
|
64
|
+
"stdin-filepath": stdinFilepath,
|
|
65
|
+
"dump-ast": dumpAst,
|
|
19
66
|
} = parseArgs(process.argv.slice(2), {
|
|
20
|
-
boolean: ["w", "write", "c", "check"],
|
|
67
|
+
boolean: ["w", "write", "c", "check", "v", "version", "h", "help", "dump-ast"],
|
|
68
|
+
string: ["stdin-filepath"],
|
|
21
69
|
number: ["print-width"],
|
|
22
70
|
});
|
|
23
71
|
|
|
24
|
-
if (
|
|
72
|
+
if (help || h) {
|
|
73
|
+
process.stdout.write(HELP);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (showVersion || v) {
|
|
78
|
+
console.log(version);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (stdinFilepath) {
|
|
83
|
+
if (write || w) {
|
|
84
|
+
console.error("--write cannot be used with --stdin-filepath");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const code = await readStdin();
|
|
88
|
+
try {
|
|
89
|
+
if (dumpAst) {
|
|
90
|
+
const ast = prettierPluginLiquidsoap.parsers.liquidsoap.parse(code);
|
|
91
|
+
process.stdout.write(JSON.stringify(ast, null, 2) + "\n");
|
|
92
|
+
} else if (check || c) {
|
|
93
|
+
const isFormatted = await prettier.check(code, {
|
|
94
|
+
parser: "liquidsoap",
|
|
95
|
+
plugins: [prettierPluginLiquidsoap],
|
|
96
|
+
printWidth,
|
|
97
|
+
});
|
|
98
|
+
process.exit(isFormatted ? 0 : 2);
|
|
99
|
+
} else {
|
|
100
|
+
const formattedCode = await prettier.format(code, {
|
|
101
|
+
parser: "liquidsoap",
|
|
102
|
+
plugins: [prettierPluginLiquidsoap],
|
|
103
|
+
printWidth,
|
|
104
|
+
});
|
|
105
|
+
process.stdout.write(formattedCode);
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error(`Error while processing ${stdinFilepath}: ${err}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (patterns.length === 0) {
|
|
25
115
|
console.error("No filename passed!");
|
|
26
116
|
process.exit(1);
|
|
27
117
|
}
|
|
28
118
|
|
|
29
|
-
const files =
|
|
119
|
+
const files = (
|
|
120
|
+
await BluebirdPromise.map(patterns, (pattern) => glob(pattern))
|
|
121
|
+
).flat();
|
|
30
122
|
|
|
31
|
-
if (files.length > 1 && !w && !write) {
|
|
123
|
+
if (files.length > 1 && !w && !write && !dumpAst && !c && !check) {
|
|
32
124
|
console.error(
|
|
33
|
-
"-w|--write must be used when
|
|
125
|
+
"-w|--write or -c|--check must be used when passing more than one file!",
|
|
34
126
|
);
|
|
35
127
|
process.exit(1);
|
|
36
128
|
}
|
|
37
129
|
|
|
38
130
|
if (files.length === 0) {
|
|
39
|
-
console.error("No file
|
|
131
|
+
console.error("No file found matching the given pattern(s)!");
|
|
40
132
|
process.exit(1);
|
|
41
133
|
}
|
|
42
134
|
|
|
@@ -51,7 +143,10 @@ const run = async () => {
|
|
|
51
143
|
const code = "" + fs.readFileSync(file);
|
|
52
144
|
|
|
53
145
|
try {
|
|
54
|
-
if (
|
|
146
|
+
if (dumpAst) {
|
|
147
|
+
const ast = prettierPluginLiquidsoap.parsers.liquidsoap.parse(code);
|
|
148
|
+
process.stdout.write(JSON.stringify(ast, null, 2) + "\n");
|
|
149
|
+
} else if (check || c) {
|
|
55
150
|
const isFormatted = await prettier.check(code, {
|
|
56
151
|
parser: "liquidsoap",
|
|
57
152
|
plugins: [prettierPluginLiquidsoap],
|
|
@@ -66,7 +161,7 @@ const run = async () => {
|
|
|
66
161
|
});
|
|
67
162
|
|
|
68
163
|
if (write || w) {
|
|
69
|
-
console.log(`
|
|
164
|
+
console.log(`Writing formatted ${file}`);
|
|
70
165
|
fs.writeFileSync(file, formattedCode);
|
|
71
166
|
} else {
|
|
72
167
|
process.stdout.write(formattedCode);
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
audio = ()
|
|
2
|
+
let audio.encode = ()
|
|
3
|
+
|
|
4
|
+
# Encode audio track to pcm_s16
|
|
5
|
+
# @category Source / Audio processing
|
|
6
|
+
def audio.encode.pcm_s16(~id=null("audio.encode.pcm_s16"), s) =
|
|
7
|
+
let {audio, ...tracks} = source.tracks(s)
|
|
8
|
+
source(id=id, tracks.{audio = track.encode.audio.pcm_s16(audio)})
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Encode audio track to pcm_f32
|
|
12
|
+
# @category Source / Audio processing
|
|
13
|
+
def audio.encode.pcm_f32(~id=null("audio.encode.pcm_f32"), s) =
|
|
14
|
+
let {audio, ...tracks} = source.tracks(s)
|
|
15
|
+
source(id=id, tracks.{audio = track.encode.audio.pcm_f32(audio)})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
let audio.decode = ()
|
|
19
|
+
|
|
20
|
+
# Decode audio track to pcm_s16
|
|
21
|
+
# @category Source / Audio processing
|
|
22
|
+
def audio.decode.pcm_s16(~id=null("audio.decode.pcm_s16"), s) =
|
|
23
|
+
let {audio, ...tracks} = source.tracks(s)
|
|
24
|
+
source(id=id, tracks.{audio = track.decode.audio.pcm_s16(audio)})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Decode audio track to pcm_f32
|
|
28
|
+
# @category Source / Audio processing
|
|
29
|
+
def audio.decode.pcm_f32(~id=null("audio.decode.pcm_f32"), s) =
|
|
30
|
+
let {audio, ...tracks} = source.tracks(s)
|
|
31
|
+
source(id=id, tracks.{audio = track.decode.audio.pcm_f32(audio)})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Samplerate for audio.
|
|
35
|
+
# @category Settings
|
|
36
|
+
def audio.samplerate =
|
|
37
|
+
settings.frame.audio.samplerate
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Channels for audio.
|
|
41
|
+
# @category Settings
|
|
42
|
+
def audio.channels =
|
|
43
|
+
settings.frame.audio.channels
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Multiply the amplitude of the signal.
|
|
47
|
+
# @category Source / Audio processing
|
|
48
|
+
# @param f Multiplicative factor.
|
|
49
|
+
# @argsof track.audio.amplify
|
|
50
|
+
def amplify(~id=null("amplify"), %argsof(track.audio.amplify[!id]), f, s) =
|
|
51
|
+
tracks = source.tracks(s)
|
|
52
|
+
source(
|
|
53
|
+
id=id,
|
|
54
|
+
tracks.{
|
|
55
|
+
audio =
|
|
56
|
+
track.audio.amplify(
|
|
57
|
+
id=null("track.audio.amplify"),
|
|
58
|
+
%argsof(track.audio.amplify[!id]),
|
|
59
|
+
f,
|
|
60
|
+
tracks.audio
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Clip samples, i.e. ensure that all values are between
|
|
67
|
+
# `-1` and `1`: values lower than `-1` become `-1` and
|
|
68
|
+
# values higher than `1` become `1`. `nan` values become `0`.
|
|
69
|
+
# @category Source / Audio processing
|
|
70
|
+
# @argsof track.audio.clip
|
|
71
|
+
def clip(~id=null("clip"), %argsof(track.audio.clip[!id]), s) =
|
|
72
|
+
tracks = source.tracks(s)
|
|
73
|
+
source(
|
|
74
|
+
id=id,
|
|
75
|
+
tracks.{audio = track.audio.clip(%argsof(track.audio.clip), tracks.audio)}
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
lufs_builtin = lufs
|
|
80
|
+
|
|
81
|
+
# Normalization the volume of a stream (this is also called _automatic gain
|
|
82
|
+
# control_). Dynamic normalization of the signal is sometimes the only option
|
|
83
|
+
# (for instance, for live sources), and can make a listening experience much
|
|
84
|
+
# nicer. However, its dynamic aspect implies some limitations which can go as
|
|
85
|
+
# far as creating saturation in some extreme cases. If possible, consider using
|
|
86
|
+
# some track-based normalization techniques such as those based on
|
|
87
|
+
# ReplayGain. The implementation of Liquidsoap < 2.0 was renamed to
|
|
88
|
+
# `normalize.old`.
|
|
89
|
+
# @category Source / Audio processing
|
|
90
|
+
# @param ~id Force the value of the source ID.
|
|
91
|
+
# @param ~gain_max Maximal gain value (dB).
|
|
92
|
+
# @param ~gain_min Minimal gain value (dB).
|
|
93
|
+
# @param ~down Characteristic time to go down.
|
|
94
|
+
# @param ~up Characteristic time to go up.
|
|
95
|
+
# @param ~lookahead How much time to look ahead of the signal (second). Setting a positive value delays the output by the corresponding amount of time.
|
|
96
|
+
# @param ~lufs Use LUFS instead of RMS to compute intensity.
|
|
97
|
+
# @param ~target Desired RMS (dB).
|
|
98
|
+
# @param ~threshold Minimal RMS for activaing gain control (dB).
|
|
99
|
+
# @param ~window Duration of the window used to compute the current RMS power (second).
|
|
100
|
+
# @param ~enabled Whether normalization is enabled or not.
|
|
101
|
+
# @param ~debug How often to print debug messages, in seconds, useful to finetune the parameters. You should set `set("log.level", 5)` to see them.
|
|
102
|
+
# @param s Source to normalize.
|
|
103
|
+
# @method gain Current amplification coefficient (in linear scale).
|
|
104
|
+
# @method target_gain Current target amplification coefficient (in linear scale).
|
|
105
|
+
# @method rms Current rms (in linear scale).
|
|
106
|
+
def replaces normalize(
|
|
107
|
+
~id=null,
|
|
108
|
+
~target=getter(-13.),
|
|
109
|
+
~up=getter(10.),
|
|
110
|
+
~down=getter(.1),
|
|
111
|
+
~gain_min=-12.,
|
|
112
|
+
~gain_max=12.,
|
|
113
|
+
~lufs=false,
|
|
114
|
+
~lookahead=getter(0.),
|
|
115
|
+
~window=getter(.5),
|
|
116
|
+
~threshold=getter(-40.),
|
|
117
|
+
~track_sensitive=true,
|
|
118
|
+
~enabled=getter(true),
|
|
119
|
+
~debug=null,
|
|
120
|
+
s
|
|
121
|
+
) =
|
|
122
|
+
let (s, rms) =
|
|
123
|
+
if
|
|
124
|
+
lufs
|
|
125
|
+
then
|
|
126
|
+
s = lufs_builtin(id=id, window=window, s)
|
|
127
|
+
(s, {lin_of_dB(s.lufs())})
|
|
128
|
+
else
|
|
129
|
+
s = rms.smooth(id=id, duration=window, s)
|
|
130
|
+
(s, s.rms)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
v = ref(1.)
|
|
134
|
+
frame = frame.duration()
|
|
135
|
+
gain_min = lin_of_dB(gain_min)
|
|
136
|
+
gain_max = lin_of_dB(gain_max)
|
|
137
|
+
|
|
138
|
+
def update() =
|
|
139
|
+
if
|
|
140
|
+
not (getter.get(enabled))
|
|
141
|
+
then
|
|
142
|
+
v := 1.
|
|
143
|
+
else
|
|
144
|
+
target = lin_of_dB(getter.get(target))
|
|
145
|
+
threshold = lin_of_dB(getter.get(threshold))
|
|
146
|
+
rms = rms()
|
|
147
|
+
if
|
|
148
|
+
rms >= threshold
|
|
149
|
+
then
|
|
150
|
+
if
|
|
151
|
+
v() * rms <= target
|
|
152
|
+
then
|
|
153
|
+
up = 1. - exp(0. - frame / getter.get(up))
|
|
154
|
+
v := v() + up * ((target / rms) - v())
|
|
155
|
+
else
|
|
156
|
+
down = 1. - exp(0. - frame / getter.get(down))
|
|
157
|
+
v := v() + down * ((target / rms) - v())
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
v := max(gain_min, min(gain_max, v()))
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def target_gain() =
|
|
166
|
+
lin_of_dB(getter.get(target)) / rms()
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
s =
|
|
170
|
+
if
|
|
171
|
+
null.defined(debug)
|
|
172
|
+
then
|
|
173
|
+
source.run(
|
|
174
|
+
s,
|
|
175
|
+
every=null.get(debug),
|
|
176
|
+
{
|
|
177
|
+
log.debug(
|
|
178
|
+
"rms: #{rms()} / #{lin_of_dB(getter.get(target))}\tgain: #{v()} / #{
|
|
179
|
+
target_gain()
|
|
180
|
+
}"
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
else
|
|
185
|
+
s
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
s = source.methods(s)
|
|
189
|
+
s.on_frame(synchronous=true, update)
|
|
190
|
+
if track_sensitive then s.on_track(synchronous=true, fun (_) -> v := 1.) end
|
|
191
|
+
amplify(id=id, {v()}, delay_line(lookahead, s)).{
|
|
192
|
+
rms = rms,
|
|
193
|
+
gain = {v()},
|
|
194
|
+
target_gain = target_gain
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Swap two channels of a stereo source.
|
|
199
|
+
# @category Source / Conversion
|
|
200
|
+
def swap(id=null("swap"), s) =
|
|
201
|
+
tracks = source.tracks(s)
|
|
202
|
+
source(id=id, tracks.{audio = track.audio.swap(tracks.audio)})
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Produce mono audio by taking the mean of all audio channels.
|
|
206
|
+
# @category Source / Conversion
|
|
207
|
+
# @argsof track.audio.mean
|
|
208
|
+
def mean(~id=null("mean"), %argsof(track.audio.mean[!id]), s) =
|
|
209
|
+
tracks = source.tracks(s)
|
|
210
|
+
source(
|
|
211
|
+
id=id,
|
|
212
|
+
tracks.{audio = track.audio.mean(%argsof(track.audio.mean), tracks.audio)}
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Convert any pcm audio source into a stereo source.
|
|
217
|
+
# @category Source / Conversion
|
|
218
|
+
def stereo(~id=null("stereo"), s) =
|
|
219
|
+
tracks = source.tracks(s)
|
|
220
|
+
source(id=id, tracks.{audio = track.audio.stereo(tracks.audio)})
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Extract the left channel of a stereo track
|
|
224
|
+
# @category Source / Conversion
|
|
225
|
+
# @param t Track to extract from
|
|
226
|
+
def track.audio.stereo.left(~id=null("track.audio.stereo.left"), t) =
|
|
227
|
+
track.audio.amplify(
|
|
228
|
+
id=id,
|
|
229
|
+
override=null,
|
|
230
|
+
2.,
|
|
231
|
+
track.audio.mean(track.audio.stereo.pan(-1., t))
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Extract the left channel of a stereo source
|
|
236
|
+
# @category Source / Conversion
|
|
237
|
+
# @param s Source to extract from
|
|
238
|
+
def stereo.left(~id=null("stereo.left"), s) =
|
|
239
|
+
tracks = source.tracks(s)
|
|
240
|
+
source(id=id, tracks.{audio = track.audio.stereo.left(tracks.audio)})
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Extract the right channel of a stereo track
|
|
244
|
+
# @category Source / Conversion
|
|
245
|
+
# @param s Track to extract from
|
|
246
|
+
def track.audio.stereo.right(~id=null("track.audio.stereo.right"), t) =
|
|
247
|
+
track.audio.amplify(
|
|
248
|
+
id=id,
|
|
249
|
+
override=null,
|
|
250
|
+
2.,
|
|
251
|
+
track.audio.mean(track.audio.stereo.pan(1., t))
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Extract the right channel of a stereo source
|
|
256
|
+
# @category Source / Conversion
|
|
257
|
+
# @param s Source to extract from
|
|
258
|
+
def stereo.right(~id=null("stereo.right"), s) =
|
|
259
|
+
tracks = source.tracks(s)
|
|
260
|
+
source(id=id, tracks.{audio = track.audio.stereo.right(tracks.audio)})
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Spacializer which allows controlling the width of the signal.
|
|
264
|
+
# @category Source / Audio processing
|
|
265
|
+
# @param w Width of the signal (-1: mono, 0.: original, 1.: wide stereo).
|
|
266
|
+
def stereo.width(~id=null("stereo.width"), w=getter(0.), (s:source)) =
|
|
267
|
+
tracks = source.tracks(s)
|
|
268
|
+
source(id=id, tracks.{audio = track.audio.stereo.width(w, tracks.audio)})
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Pan a stereo sound.
|
|
272
|
+
# @category Source / Audio processing
|
|
273
|
+
# @argsof track.audio.stereo.pan
|
|
274
|
+
# @param pan Pan value. Should be between `-1` (left side) and `1` (right side).
|
|
275
|
+
def stereo.pan(
|
|
276
|
+
~id=null("stereo.pan"),
|
|
277
|
+
%argsof(track.audio.stereo.pan[!id]),
|
|
278
|
+
pan,
|
|
279
|
+
(s:source)
|
|
280
|
+
) =
|
|
281
|
+
tracks = source.tracks(s)
|
|
282
|
+
source(
|
|
283
|
+
id=id,
|
|
284
|
+
tracks.{
|
|
285
|
+
audio =
|
|
286
|
+
track.audio.stereo.pan(
|
|
287
|
+
%argsof(track.audio.stereo.pan),
|
|
288
|
+
pan,
|
|
289
|
+
tracks.audio
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Slow down or accelerate an audio stream by stretching (sounds lower) or squeezing it (sounds higher).
|
|
296
|
+
# @category Source / Audio processing
|
|
297
|
+
# @argsof track.audio.stretch
|
|
298
|
+
def stretch(
|
|
299
|
+
~id=null("stretch"),
|
|
300
|
+
%argsof(track.audio.stretch[!id]),
|
|
301
|
+
(s:source)
|
|
302
|
+
) =
|
|
303
|
+
tracks = source.tracks(s)
|
|
304
|
+
source.audio(
|
|
305
|
+
id=id,
|
|
306
|
+
track.audio.stretch(%argsof(track.audio.stretch), tracks.audio)
|
|
307
|
+
)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
let stereo.ms = ()
|
|
311
|
+
|
|
312
|
+
# Decode mid+side stereo (M/S) to left+right stereo.
|
|
313
|
+
# @category Source / Audio processing
|
|
314
|
+
# @argsof track.audio.stereo.ms.decode
|
|
315
|
+
def stereo.ms.decode(
|
|
316
|
+
~id=null("stereo.ms.decode"),
|
|
317
|
+
%argsof(track.audio.stereo.ms.decode[!id]),
|
|
318
|
+
(s:source)
|
|
319
|
+
) =
|
|
320
|
+
tracks = source.tracks(s)
|
|
321
|
+
source(
|
|
322
|
+
id=id,
|
|
323
|
+
tracks.{
|
|
324
|
+
audio =
|
|
325
|
+
track.audio.stereo.ms.decode(
|
|
326
|
+
%argsof(track.audio.stereo.ms.decode),
|
|
327
|
+
tracks.audio
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Encode left+right stereo to mid+side stereo (M/S).
|
|
334
|
+
# @category Source / Audio processing
|
|
335
|
+
# @argsof track.audio.stereo.ms.encode
|
|
336
|
+
def stereo.ms.encode(
|
|
337
|
+
~id=null("stereo.ms.encode"),
|
|
338
|
+
%argsof(track.audio.stereo.ms.encode[!id]),
|
|
339
|
+
(s:source)
|
|
340
|
+
) =
|
|
341
|
+
tracks = source.tracks(s)
|
|
342
|
+
source(
|
|
343
|
+
id=id,
|
|
344
|
+
tracks.{
|
|
345
|
+
audio =
|
|
346
|
+
track.audio.stereo.ms.encode(
|
|
347
|
+
%argsof(track.audio.stereo.ms.encode),
|
|
348
|
+
tracks.audio
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
%ifdef track.audio.stereotool
|
|
355
|
+
# Process an audio source using stereotool
|
|
356
|
+
# @argsof track.audio.stereotool
|
|
357
|
+
# @category Source / Audio processing
|
|
358
|
+
def stereotool(
|
|
359
|
+
~id=null("stereotool"),
|
|
360
|
+
%argsof(track.audio.stereotool[!id]),
|
|
361
|
+
s
|
|
362
|
+
) =
|
|
363
|
+
let {audio, metadata, track_marks, ..._} = source.tracks(s)
|
|
364
|
+
s = track.audio.stereotool(%argsof(track.audio.stereotool[!id]), audio)
|
|
365
|
+
let replaces s =
|
|
366
|
+
source(
|
|
367
|
+
id=id,
|
|
368
|
+
{audio = (s : pcm), metadata = metadata, track_marks = track_marks}
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
s
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Output an audio source using stereotool
|
|
375
|
+
# @argsof track.audio.stereotool[!active]
|
|
376
|
+
# @argsof output.dummy[!id]
|
|
377
|
+
# @category Source / Output
|
|
378
|
+
def output.stereotool(
|
|
379
|
+
~id=null("output.stereotool"),
|
|
380
|
+
%argsof(track.audio.stereotool[!id,!active]),
|
|
381
|
+
%argsof(output.dummy[!id]),
|
|
382
|
+
s
|
|
383
|
+
) =
|
|
384
|
+
s = stereotool(%argsof(track.audio.stereotool[!id,!active]), active=false, s)
|
|
385
|
+
let replaces s = output.dummy(id=id, %argsof(output.dummy[!id]), s)
|
|
386
|
+
|
|
387
|
+
s
|
|
388
|
+
end
|
|
389
|
+
%endif
|
|
390
|
+
|
|
391
|
+
# Defer the source's audio track by a given amount of time. Source will be
|
|
392
|
+
# available when the given `delay` has been fully buffered. Use this operator
|
|
393
|
+
# instead of `buffer` when buffering large amount of data as initial delay.
|
|
394
|
+
#
|
|
395
|
+
# This operator encodes and decodes the audio content. See `defer.pcm_s16` for
|
|
396
|
+
# a low-level operator using directly the `pcm_s16` format.
|
|
397
|
+
# @category Source / Audio processing
|
|
398
|
+
# @argsof track.audio.defer[!id]
|
|
399
|
+
# @param ~id Force the source's ID
|
|
400
|
+
def defer(~id=null("defer"), %argsof(track.audio.defer[!id]), s) =
|
|
401
|
+
let {audio = a} = source.tracks(s)
|
|
402
|
+
a = track.encode.audio.pcm_s16(a)
|
|
403
|
+
a = track.audio.defer(%argsof(track.audio.defer[!id]), a)
|
|
404
|
+
a = track.decode.audio.pcm_s16(a)
|
|
405
|
+
|
|
406
|
+
source(
|
|
407
|
+
id=id,
|
|
408
|
+
{
|
|
409
|
+
audio = a,
|
|
410
|
+
metadata = track.metadata(a),
|
|
411
|
+
track_marks = track.track_marks(a)
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Defer the source's audio track by a given amount of time. Source will be
|
|
417
|
+
# available when the given `delay` has been fully buffered. Use this operator
|
|
418
|
+
# instead of `buffer` when buffering large amount of data as initial delay.
|
|
419
|
+
#
|
|
420
|
+
# This operator uses a source already using `pcm_s16` audio data. It can
|
|
421
|
+
# be used to prevent unneeded data copy. Typically, decoders that know
|
|
422
|
+
# how to decode to `pcm_s16` (like `ffmpeg`) will decode directly
|
|
423
|
+
# into the format and encoders who support it (also `%ffmpeg`)
|
|
424
|
+
# will encoder directly from the `pcm_s16` data. Use `defer` if you
|
|
425
|
+
# prefer a more user-friendly operator.
|
|
426
|
+
# @category Source / Audio processing
|
|
427
|
+
# @argsof track.audio.defer[!id]
|
|
428
|
+
# @param ~id Force the source's ID
|
|
429
|
+
def defer.pcm_s16(
|
|
430
|
+
~id=null("defer.pcm_s16"),
|
|
431
|
+
%argsof(track.audio.defer[!id]),
|
|
432
|
+
(s:source(audio=pcm_s16))
|
|
433
|
+
) =
|
|
434
|
+
let {audio = a} = source.tracks(s)
|
|
435
|
+
a = track.audio.defer(%argsof(track.audio.defer[!id]), a)
|
|
436
|
+
|
|
437
|
+
source(
|
|
438
|
+
id=id,
|
|
439
|
+
{
|
|
440
|
+
audio = a,
|
|
441
|
+
metadata = track.metadata(a),
|
|
442
|
+
track_marks = track.track_marks(a)
|
|
443
|
+
}
|
|
444
|
+
)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
let settings.normalize_track_gain_metadata =
|
|
448
|
+
settings.make(
|
|
449
|
+
description="Metadata used to store track gain normalization metadata",
|
|
450
|
+
"liq_normalize_track_gain"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Amplify source tracks according to track gain normalization metadata. This operator does not
|
|
454
|
+
# compute that value. You can use integrated LUFS track gain or ReplayGain to compute it.
|
|
455
|
+
# @category Source / Audio processing
|
|
456
|
+
# @param ~id Force the value of the source ID.
|
|
457
|
+
# @param s Source to be amplified.
|
|
458
|
+
def normalize_track_gain(~id=null, s) =
|
|
459
|
+
amplify(id=id, override=settings.normalize_track_gain_metadata(), 1., s)
|
|
460
|
+
end
|