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,476 @@
|
|
|
1
|
+
# Match a string with an expression. Perl compatible regular expressions are
|
|
2
|
+
# recognized. Hence, special characters should be escaped. Alternatively, one
|
|
3
|
+
# can use the `r/_/.test(_)` syntax for regular expressions.
|
|
4
|
+
# @category String
|
|
5
|
+
def string.match(~pattern, s) =
|
|
6
|
+
regexp(pattern).test(s)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Extract substrings from a string. Perl compatible regular expressions are
|
|
10
|
+
# recognized. Hence, special characters should be escaped. Returns a list of
|
|
11
|
+
# (index,value). If the list does not have a pair associated to some index, it
|
|
12
|
+
# means that the corresponding pattern was not found. Alter natively, one can
|
|
13
|
+
# use the `r/_/.exec(_)` syntax for regular expressions.
|
|
14
|
+
# @category String
|
|
15
|
+
def string.extract(~pattern, s) =
|
|
16
|
+
regexp(pattern).exec(s)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Replace all substrings matched by a pattern by another string returned by a
|
|
20
|
+
# function. Alternatively, one can use the `r/_/g.replace(_)` syntax for regular
|
|
21
|
+
# expressions.
|
|
22
|
+
# @category String
|
|
23
|
+
# @param ~pattern Pattern (regular expression) of substrings which should be replaced.
|
|
24
|
+
# @param f Function getting a matched substring an returning the string to replace it with.
|
|
25
|
+
# @param s String whose substrings should be replaced.
|
|
26
|
+
def string.replace(~pattern, f, s) =
|
|
27
|
+
regexp(flags=["g"], pattern).replace(f, s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Split a string at "separator". Perl compatible regular expressions are
|
|
31
|
+
# recognized. Hence, special characters should be escaped. Alternatively, one
|
|
32
|
+
# can use the `r/_/.split(_)` syntax for regular expressions.
|
|
33
|
+
# @category String
|
|
34
|
+
def string.split(~separator, s) =
|
|
35
|
+
regexp(separator).split(s)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return an array of the string's bytes.
|
|
39
|
+
# @category String
|
|
40
|
+
def string.bytes(s) =
|
|
41
|
+
string.split(separator="", s)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Return the length of the string in bytes.
|
|
45
|
+
# @category String
|
|
46
|
+
def string.bytes.length(s) =
|
|
47
|
+
string.length(encoding="ascii", s)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Split a string in two at first "separator".
|
|
51
|
+
# @category String
|
|
52
|
+
def string.split.first(~encoding=null, ~separator, s) =
|
|
53
|
+
n = string.length(encoding=encoding, s)
|
|
54
|
+
l = string.length(encoding=encoding, separator)
|
|
55
|
+
i = string.index(substring=separator, s)
|
|
56
|
+
if
|
|
57
|
+
i < 0
|
|
58
|
+
then
|
|
59
|
+
error.raise(
|
|
60
|
+
error.not_found,
|
|
61
|
+
"String does not contain the separator."
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
(
|
|
66
|
+
string.sub(encoding=encoding, s, start=0, length=i),
|
|
67
|
+
string.sub(encoding=encoding, s, start=i + l, length=n - (i + l))
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Test whether a string contains a given prefix, substring or suffix.
|
|
72
|
+
# @category String
|
|
73
|
+
# @param ~encoding Encoding used to split characters. Should be one of: `"utf8"` or `"ascii"`
|
|
74
|
+
# @param ~prefix Prefix to look for.
|
|
75
|
+
# @param ~substring Substring to look for.
|
|
76
|
+
# @param ~suffix Suffix to look for.
|
|
77
|
+
# @param s The string to look into.
|
|
78
|
+
def string.contains(~encoding=null, ~prefix="", ~substring="", ~suffix="", s) =
|
|
79
|
+
ans = ref(prefix == "" and substring == "" and suffix == "")
|
|
80
|
+
if
|
|
81
|
+
prefix != ""
|
|
82
|
+
then
|
|
83
|
+
ans :=
|
|
84
|
+
ans()
|
|
85
|
+
or string.sub(
|
|
86
|
+
encoding=encoding,
|
|
87
|
+
s,
|
|
88
|
+
start=0,
|
|
89
|
+
length=string.length(encoding=encoding, prefix)
|
|
90
|
+
) ==
|
|
91
|
+
prefix
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if
|
|
95
|
+
suffix != ""
|
|
96
|
+
then
|
|
97
|
+
suflen = string.length(encoding=encoding, suffix)
|
|
98
|
+
ans :=
|
|
99
|
+
ans()
|
|
100
|
+
or string.sub(
|
|
101
|
+
encoding=encoding,
|
|
102
|
+
s,
|
|
103
|
+
start=string.length(encoding=encoding, s) - suflen,
|
|
104
|
+
length=suflen
|
|
105
|
+
) ==
|
|
106
|
+
suffix
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if
|
|
110
|
+
substring != ""
|
|
111
|
+
then
|
|
112
|
+
sublen = string.length(encoding=encoding, substring)
|
|
113
|
+
for i = 0 to string.length(encoding=encoding, s) - sublen do
|
|
114
|
+
ans :=
|
|
115
|
+
ans()
|
|
116
|
+
or (
|
|
117
|
+
string.sub(encoding=encoding, s, start=i, length=sublen) == substring
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
ans()
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# What remains of a string after a given prefix.
|
|
127
|
+
# @category String
|
|
128
|
+
# @param ~encoding Encoding used to split characters. Should be one of: `"utf8"` or `"ascii"`
|
|
129
|
+
# @param ~prefix Requested prefix.
|
|
130
|
+
def string.residual(~encoding=null, ~prefix, s) =
|
|
131
|
+
n = string.length(encoding=encoding, prefix)
|
|
132
|
+
if
|
|
133
|
+
string.contains(encoding=encoding, prefix=prefix, s)
|
|
134
|
+
then
|
|
135
|
+
string.sub(
|
|
136
|
+
encoding=encoding,
|
|
137
|
+
s,
|
|
138
|
+
start=n,
|
|
139
|
+
length=string.length(encoding=encoding, s) - n
|
|
140
|
+
)
|
|
141
|
+
else
|
|
142
|
+
null
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Test whether a string is a valid integer.
|
|
147
|
+
# @category String
|
|
148
|
+
def string.is_int(s) =
|
|
149
|
+
try
|
|
150
|
+
ignore(int_of_string(s))
|
|
151
|
+
true
|
|
152
|
+
catch _ do
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Convert a string to a int.
|
|
158
|
+
# @category String
|
|
159
|
+
def string.to_int(~default=0, s) =
|
|
160
|
+
int_of_string(default=default, s)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Convert an int to string.
|
|
164
|
+
# @category String
|
|
165
|
+
# @param ~digits Minimal number of digits (pad with 0s on the left if necessary).
|
|
166
|
+
def string.of_int(~digits=0, n) =
|
|
167
|
+
s = string(n)
|
|
168
|
+
if
|
|
169
|
+
string.length(s) >= digits
|
|
170
|
+
then
|
|
171
|
+
s
|
|
172
|
+
else
|
|
173
|
+
string.make(char_code=48, digits - string.bytes.length(s)) ^ s
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Convert a string to a float.
|
|
178
|
+
# @category String
|
|
179
|
+
def string.to_float(~default=0., s) =
|
|
180
|
+
float_of_string(default=default, s)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
let string.binary = ()
|
|
184
|
+
|
|
185
|
+
# Value of a positive (unsigned) integer encoded using native memory
|
|
186
|
+
# representation.
|
|
187
|
+
# @category String
|
|
188
|
+
# @param ~little_endian Whether the memory representation is little endian.
|
|
189
|
+
# @param s String containing the binary representation.
|
|
190
|
+
def string.binary.to_int(~little_endian=true, s) =
|
|
191
|
+
ans = ref(0)
|
|
192
|
+
n = string.bytes.length(s)
|
|
193
|
+
for i = 0 to n - 1 do
|
|
194
|
+
ans :=
|
|
195
|
+
lsl(ans(), 8) + string.nth(s, if little_endian then n - 1 - i else i end)
|
|
196
|
+
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
ans()
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Encode a positive (unsigned) integer using native memory representation.
|
|
203
|
+
# @category String
|
|
204
|
+
# @param ~pad Minimum length in digits (pad on the left with zeros in order to reach it)
|
|
205
|
+
# @param ~little_endian Whether the memory representation is little endian.
|
|
206
|
+
# @param s String containing the binary representation.
|
|
207
|
+
def string.binary.of_int(~pad=0, ~little_endian=true, d) =
|
|
208
|
+
def rec f(d, s) =
|
|
209
|
+
if
|
|
210
|
+
d > 0
|
|
211
|
+
then
|
|
212
|
+
c = string.hex_of_int(pad=2, (d mod 256))
|
|
213
|
+
if
|
|
214
|
+
little_endian
|
|
215
|
+
then
|
|
216
|
+
f(lsr(d, 8), "#{s}\\x#{c}")
|
|
217
|
+
else
|
|
218
|
+
f(lsr(d, 8), "\\x#{c}#{s}")
|
|
219
|
+
end
|
|
220
|
+
else
|
|
221
|
+
s
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
ret = d == 0 ? "\\x00" : f(d, "")
|
|
226
|
+
ret = string.unescape(ret)
|
|
227
|
+
len = string.bytes.length(ret)
|
|
228
|
+
if
|
|
229
|
+
len < pad
|
|
230
|
+
then
|
|
231
|
+
ans = string.make(char_code=0, pad - len)
|
|
232
|
+
if little_endian then "#{ret}#{ans}" else "#{ans}#{ret}" end
|
|
233
|
+
else
|
|
234
|
+
ret
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Add a null character at the end of a string.
|
|
239
|
+
# @category String
|
|
240
|
+
# @param s String.
|
|
241
|
+
def string.null_terminated(s) =
|
|
242
|
+
s ^ "\000"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Generate an identifier if no identifier was provided.
|
|
246
|
+
# @category String
|
|
247
|
+
# @param ~default Name from which identifier is generated if not present.
|
|
248
|
+
# @param id Proposed identifier.
|
|
249
|
+
def string.id.default(~default, id) =
|
|
250
|
+
null.default(id, {string.id(default)})
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Return a quoted copy of the given string.
|
|
254
|
+
# By default, the string is assumed to be `"utf8"` encoded and is escaped
|
|
255
|
+
# following JSON and javascript specification.
|
|
256
|
+
# @category String
|
|
257
|
+
# @param ~encoding One of: `"ascii"` or `"utf8"`. If `null`, `utf8` is tried first and `ascii` is used as a fallback if this fails.
|
|
258
|
+
def string.quote(~encoding=null, s) =
|
|
259
|
+
def special_char(~encoding, s) =
|
|
260
|
+
if
|
|
261
|
+
s == "'"
|
|
262
|
+
then
|
|
263
|
+
false
|
|
264
|
+
else
|
|
265
|
+
string.escape.special_char(encoding=encoding, s)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
s = string.escape(special_char=special_char, encoding=encoding, s)
|
|
270
|
+
"\"#{s}\""
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Return an unquoted copy of the given string.
|
|
274
|
+
# Quotes are removed by trying to parse the string
|
|
275
|
+
# following the JSON string escaping convention.
|
|
276
|
+
# @category String
|
|
277
|
+
def string.unquote(s) =
|
|
278
|
+
try
|
|
279
|
+
let json.parse (s : string) = s
|
|
280
|
+
s
|
|
281
|
+
catch _ do
|
|
282
|
+
s
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
let string.data_uri = ()
|
|
287
|
+
|
|
288
|
+
# Encode a string using the data uri format,
|
|
289
|
+
# i.e. `"data:<mime>[;base64],<data>"`.
|
|
290
|
+
# @category String
|
|
291
|
+
# @param ~base64 Encode data using the base64 format
|
|
292
|
+
# @param ~mime Data mime type
|
|
293
|
+
def string.data_uri.encode(~base64=true, ~(mime:string), s) =
|
|
294
|
+
s = base64 ? ";base64,#{string.base64.encode(s)}" : ",#{s}"
|
|
295
|
+
"data:#{mime}#{s}"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Decode a string using the data uri format,
|
|
299
|
+
# i.e. `"data:<mime>[;base64],<data>"`.
|
|
300
|
+
# @category String
|
|
301
|
+
def string.data_uri.decode(s) =
|
|
302
|
+
captured =
|
|
303
|
+
r/^data:(?<mime>[\/\w]+)(?<base64>;base64)?,(?<data>.+)$/.exec(s).groups
|
|
304
|
+
|
|
305
|
+
if
|
|
306
|
+
list.assoc.mem("mime", captured) and list.assoc.mem("data", captured)
|
|
307
|
+
then
|
|
308
|
+
mime = list.assoc("mime", captured)
|
|
309
|
+
data = list.assoc("data", captured)
|
|
310
|
+
data =
|
|
311
|
+
if
|
|
312
|
+
list.assoc.mem("base64", captured)
|
|
313
|
+
then
|
|
314
|
+
string.base64.decode(data)
|
|
315
|
+
else
|
|
316
|
+
data
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
data.{mime = mime}
|
|
320
|
+
else
|
|
321
|
+
null
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
let string.getter = ()
|
|
326
|
+
|
|
327
|
+
# Create a string getter which will return once the given string and then the
|
|
328
|
+
# empty string.
|
|
329
|
+
# @category String
|
|
330
|
+
def string.getter.single(s) =
|
|
331
|
+
first = ref(true)
|
|
332
|
+
fun () ->
|
|
333
|
+
begin
|
|
334
|
+
if
|
|
335
|
+
first()
|
|
336
|
+
then
|
|
337
|
+
first := false
|
|
338
|
+
s
|
|
339
|
+
else
|
|
340
|
+
""
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Flush all values from a string getter and return
|
|
346
|
+
# the concatenated result. If the getter is constant,
|
|
347
|
+
# return the constant string. Otherwise, call the getter
|
|
348
|
+
# repeatedly until it returns an empty string and return
|
|
349
|
+
# the concatenated result
|
|
350
|
+
# @category String
|
|
351
|
+
def string.getter.flush(~separator="", gen) =
|
|
352
|
+
if
|
|
353
|
+
getter.is_constant(gen)
|
|
354
|
+
then
|
|
355
|
+
getter.get(gen)
|
|
356
|
+
else
|
|
357
|
+
def rec f(data) =
|
|
358
|
+
chunk = getter.get(gen)
|
|
359
|
+
if
|
|
360
|
+
chunk == ""
|
|
361
|
+
then
|
|
362
|
+
string.concat(separator=separator, data)
|
|
363
|
+
else
|
|
364
|
+
f([...data, chunk])
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
f([])
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Combine a list of string getters `[g1, ...]`
|
|
373
|
+
# and return a single getter `g` such that:
|
|
374
|
+
# `string.getter.flush(separator=s, g) = string.concat(separator=s, list.filter(fun (s) -> s != "", [string.getter.flush(g1), ...]))`
|
|
375
|
+
# @category String
|
|
376
|
+
def string.getter.concat(l) =
|
|
377
|
+
len = list.length(l)
|
|
378
|
+
pos = ref(0)
|
|
379
|
+
|
|
380
|
+
def rec f() =
|
|
381
|
+
if
|
|
382
|
+
pos() == len
|
|
383
|
+
then
|
|
384
|
+
""
|
|
385
|
+
else
|
|
386
|
+
gen = list.nth(l, pos())
|
|
387
|
+
ret = getter.get(gen)
|
|
388
|
+
if ret == "" or getter.is_constant(gen) then ref.incr(pos) end
|
|
389
|
+
ret == "" ? f() : ret
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
getter(f)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
let string.char.ascii = ()
|
|
397
|
+
|
|
398
|
+
# All ASCII characters code
|
|
399
|
+
# @category String
|
|
400
|
+
let string.char.ascii = list.init(128, fun (c) -> c)
|
|
401
|
+
|
|
402
|
+
# All ASCII control character codes
|
|
403
|
+
# @category String
|
|
404
|
+
let string.char.ascii.control = list.init(32, fun (c) -> c)
|
|
405
|
+
|
|
406
|
+
# All ASCII printable character codes
|
|
407
|
+
# @category String
|
|
408
|
+
let string.char.ascii.printable = list.init(96, fun (c) -> c + 32)
|
|
409
|
+
|
|
410
|
+
# All ASCII alphabet character codes
|
|
411
|
+
# @category String
|
|
412
|
+
let string.char.ascii.alphabet =
|
|
413
|
+
[
|
|
414
|
+
# A-Z
|
|
415
|
+
...list.init(24, fun (c) -> c + 65),
|
|
416
|
+
# a-z
|
|
417
|
+
...list.init(24, fun (c) -> c + 97)
|
|
418
|
+
]
|
|
419
|
+
|
|
420
|
+
# All ASCII number character codes
|
|
421
|
+
# @category String
|
|
422
|
+
let string.char.ascii.number = list.init(10, fun (c) -> c + 48)
|
|
423
|
+
|
|
424
|
+
# Return a random ASCII character
|
|
425
|
+
# @category String
|
|
426
|
+
def string.char.ascii.random(range=[...string.char.ascii]) =
|
|
427
|
+
string.char(list.nth(range, random.int(min=0, max=list.length(range) - 1)))
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Escape HTML entities.
|
|
431
|
+
# @category String
|
|
432
|
+
# @argsof string.escape[encoding]
|
|
433
|
+
def string.escape.html(%argsof(string.escape[encoding]), s) =
|
|
434
|
+
escaped =
|
|
435
|
+
[
|
|
436
|
+
("&", "&"),
|
|
437
|
+
("<", "<"),
|
|
438
|
+
(">", ">"),
|
|
439
|
+
('"', """),
|
|
440
|
+
("'", "'")
|
|
441
|
+
]
|
|
442
|
+
|
|
443
|
+
def special_char(~encoding:_, c) =
|
|
444
|
+
list.assoc.mem(c, escaped)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def escape_char(~encoding:_, c) =
|
|
448
|
+
escaped[c]
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
string.escape(
|
|
452
|
+
%argsof(string.escape[encoding]),
|
|
453
|
+
special_char=special_char,
|
|
454
|
+
escape_char=escape_char,
|
|
455
|
+
s
|
|
456
|
+
)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Generate a given number of spaces (this can be useful for indenting).
|
|
460
|
+
# @category String
|
|
461
|
+
# @param n Number of spaces.
|
|
462
|
+
def string.spaces(n) =
|
|
463
|
+
string.make(char_code=32, n)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Convert a string to uppercase.
|
|
467
|
+
# @category String
|
|
468
|
+
def string.uppercase(s) =
|
|
469
|
+
string.case(lower=false, s)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# Convert a string to lowercase.
|
|
473
|
+
# @category String
|
|
474
|
+
def string.lowercase(s) =
|
|
475
|
+
string.case(lower=true, s)
|
|
476
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# At the beginning of each track, select the first ready child.
|
|
2
|
+
# @category Source / Track processing
|
|
3
|
+
# @param ~id Force the value of the source ID.
|
|
4
|
+
def fallback(~id=null, sources) =
|
|
5
|
+
switch(id=id, list.map(fun (s) -> ({true}, s), sources))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Rotate between sources.
|
|
9
|
+
# @category Source / Track processing
|
|
10
|
+
# @param ~id Force the value of the source ID.
|
|
11
|
+
def rotate(~id=null, sources) =
|
|
12
|
+
sources =
|
|
13
|
+
(sources :
|
|
14
|
+
[
|
|
15
|
+
source.{
|
|
16
|
+
on_select?: (
|
|
17
|
+
{ending: source?, replay_metadata: bool, starting: source}
|
|
18
|
+
)->source,
|
|
19
|
+
on_leave?: ({source: source, track_sensitive: bool})->unit,
|
|
20
|
+
single?: bool,
|
|
21
|
+
track_sensitive?: getter(bool),
|
|
22
|
+
weight?: getter(int)
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
sources = list.map(fun (s) -> s.{weight = s?.weight ?? getter(1)}, sources)
|
|
28
|
+
failed = (source.fail() : source)
|
|
29
|
+
|
|
30
|
+
# Currently selected index
|
|
31
|
+
picked_index = ref(-1)
|
|
32
|
+
|
|
33
|
+
# Number of tracks played per selected source
|
|
34
|
+
# source IDs can change between calls..
|
|
35
|
+
tracks_played =
|
|
36
|
+
list.map(fun (s) -> ((fun () -> source.id(s)), ref(0)), sources)
|
|
37
|
+
|
|
38
|
+
tracks_played =
|
|
39
|
+
fun () ->
|
|
40
|
+
list.map(
|
|
41
|
+
fun (x) ->
|
|
42
|
+
begin
|
|
43
|
+
label_fn = fst(x)
|
|
44
|
+
(label_fn(), snd(x))
|
|
45
|
+
end,
|
|
46
|
+
tracks_played
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Find index of next source to play, i.e. first ready source after currently
|
|
50
|
+
# selected one.
|
|
51
|
+
def pick() =
|
|
52
|
+
list.iter((fun (el) -> snd(el) := 0), tracks_played())
|
|
53
|
+
if
|
|
54
|
+
list.exists(source.is_ready, sources)
|
|
55
|
+
then
|
|
56
|
+
def rec f(index) =
|
|
57
|
+
s = list.nth(default=failed, sources, index)
|
|
58
|
+
if
|
|
59
|
+
source.is_ready(s)
|
|
60
|
+
then
|
|
61
|
+
picked_index := index
|
|
62
|
+
else
|
|
63
|
+
f((index + 1) mod list.length(sources))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
f((picked_index() + 1) mod list.length(sources))
|
|
68
|
+
else
|
|
69
|
+
picked_index := -1
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Add condition to i-th source.
|
|
74
|
+
def add_condition(index, s) =
|
|
75
|
+
def cond() =
|
|
76
|
+
if picked_index() == -1 then pick() end
|
|
77
|
+
if
|
|
78
|
+
picked_index() != -1
|
|
79
|
+
then
|
|
80
|
+
picked_source = list.nth(sources, picked_index())
|
|
81
|
+
fn = list.assoc(source.id(picked_source), tracks_played())
|
|
82
|
+
if getter.get(picked_source.weight) <= fn() then pick() end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
picked_index() == index
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
(cond, s)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
s = switch(list.mapi(add_condition, sources))
|
|
92
|
+
|
|
93
|
+
def f(_) =
|
|
94
|
+
if
|
|
95
|
+
null.defined(s.selected())
|
|
96
|
+
then
|
|
97
|
+
selected_id = source.id(null.get(s.selected()))
|
|
98
|
+
if
|
|
99
|
+
list.assoc.mem(selected_id, tracks_played())
|
|
100
|
+
then
|
|
101
|
+
played = list.assoc(selected_id, tracks_played())
|
|
102
|
+
ref.incr(played)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
s.on_track(synchronous=true, f)
|
|
108
|
+
|
|
109
|
+
def replaces s =
|
|
110
|
+
fallback(id=id, s::sources)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
s
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# At the beginning of every track, select a random ready child.
|
|
117
|
+
# @category Source / Track processing
|
|
118
|
+
# @param ~id Force the value of the source ID.
|
|
119
|
+
def replaces random(~id=null, sources) =
|
|
120
|
+
sources =
|
|
121
|
+
(sources :
|
|
122
|
+
[
|
|
123
|
+
source.{
|
|
124
|
+
on_select?: (
|
|
125
|
+
{ending: source?, replay_metadata: bool, starting: source}
|
|
126
|
+
)->source,
|
|
127
|
+
on_leave?: ({source: source, track_sensitive: bool})->unit,
|
|
128
|
+
single?: bool,
|
|
129
|
+
track_sensitive?: getter(bool),
|
|
130
|
+
weight?: getter(int)
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
sources = list.map(fun (s) -> s.{weight = s?.weight ?? getter(1)}, sources)
|
|
136
|
+
next_index = ref(-1)
|
|
137
|
+
|
|
138
|
+
def pick() =
|
|
139
|
+
def available_weighted_sources(cur, s) =
|
|
140
|
+
let (index, current_weight, indexes) = cur
|
|
141
|
+
let (current_weight, indexes) =
|
|
142
|
+
if
|
|
143
|
+
source.is_ready((s : source.{ weight: getter(int) }))
|
|
144
|
+
then
|
|
145
|
+
weight = getter.get(s.weight)
|
|
146
|
+
indexes = (current_weight, current_weight + weight, index)::indexes
|
|
147
|
+
(current_weight + weight, indexes)
|
|
148
|
+
else
|
|
149
|
+
(current_weight, indexes)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
(index + 1, current_weight, indexes)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
let (_, total_weight, weighted_indexes) =
|
|
156
|
+
list.fold(available_weighted_sources, (0, 0, []), sources)
|
|
157
|
+
|
|
158
|
+
picked_weight =
|
|
159
|
+
if total_weight > 0 then random.int(min=0, max=total_weight) else 0 end
|
|
160
|
+
|
|
161
|
+
def pick_source(picked_index, el) =
|
|
162
|
+
let (lower_bound, upper_bound, index) = el
|
|
163
|
+
if
|
|
164
|
+
lower_bound <= picked_weight and picked_weight < upper_bound
|
|
165
|
+
then
|
|
166
|
+
index
|
|
167
|
+
else
|
|
168
|
+
picked_index
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
next_index := list.fold(pick_source, -1, weighted_indexes)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def add_condition(index, s) =
|
|
176
|
+
def cond() =
|
|
177
|
+
if next_index() == -1 then pick() end
|
|
178
|
+
next_index() == index
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
(cond, s)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
s = switch(list.mapi(add_condition, sources))
|
|
185
|
+
|
|
186
|
+
def f(_) =
|
|
187
|
+
next_index := -1
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
s.on_track(synchronous=true, f)
|
|
191
|
+
|
|
192
|
+
def replaces s =
|
|
193
|
+
fallback(id=id, s::sources)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
s
|
|
197
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Sleep regularly, thus inducing delays in the sound production. This is mainly
|
|
2
|
+
# useful for emulating network delays or sources which are slow to produce data,
|
|
3
|
+
# and thus test bufferization and robustness of scripts.
|
|
4
|
+
# @category Source / Testing
|
|
5
|
+
# @flag experimental
|
|
6
|
+
# @param ~every How often we should sleep (in seconds, 0 means every frame).
|
|
7
|
+
# @param ~delay Delay introduced (in seconds).
|
|
8
|
+
# @param ~delay_random Maximum amount of time randomly added to the delay (in seconds).
|
|
9
|
+
# @param ~on_delay Function called when a delay is introduced, with the delay as argument.
|
|
10
|
+
# @param s Source in which the delays should be introduced.
|
|
11
|
+
# @method frozen The stream production is frozen while set to `true`.
|
|
12
|
+
def sleeper(
|
|
13
|
+
~every=1.,
|
|
14
|
+
~delay=1.1,
|
|
15
|
+
~delay_random=0.,
|
|
16
|
+
~on_delay=fun (_) -> (),
|
|
17
|
+
s
|
|
18
|
+
) =
|
|
19
|
+
last = ref(0.)
|
|
20
|
+
frozen = ref(false)
|
|
21
|
+
|
|
22
|
+
def f() =
|
|
23
|
+
while frozen() do thread.pause(0.1) end
|
|
24
|
+
now = source.time(s)
|
|
25
|
+
if
|
|
26
|
+
now >= last() + every
|
|
27
|
+
then
|
|
28
|
+
last := now
|
|
29
|
+
delay = delay + random.float(max=delay_random)
|
|
30
|
+
on_delay(delay)
|
|
31
|
+
thread.pause(delay)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
source.methods(s).on_frame(synchronous=true, f)
|
|
36
|
+
s.{frozen = frozen}
|
|
37
|
+
end
|