@adatechnology/game-audio 0.1.2 → 0.2.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/dist/index.d.ts +59 -1
- package/dist/index.global.js +114 -32
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +114 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -578,6 +578,64 @@ declare function noiseClassRecipe(row: NoiseClass, id?: string): SoundRecipe;
|
|
|
578
578
|
/** The game's classes as a bank (`sfx_pistol`... and `sfx_unknown`). Subject banks pick their own classes from `GAME_SFX_CLASSES`. */
|
|
579
579
|
declare const GAME_SFX_BANK: SoundBank;
|
|
580
580
|
|
|
581
|
+
/** A length in seconds: a number, a `[min, max]` range or a named number. */
|
|
582
|
+
type SceneLength = number | readonly [number, number] | ParamRef;
|
|
583
|
+
type SceneNoise = {
|
|
584
|
+
readonly type?: BiquadFilterType;
|
|
585
|
+
readonly f: Range;
|
|
586
|
+
/** The filter slides here over `d` (needs a fixed `d`). */
|
|
587
|
+
readonly f2?: number;
|
|
588
|
+
readonly q?: number;
|
|
589
|
+
readonly a?: number;
|
|
590
|
+
readonly d: SceneLength;
|
|
591
|
+
readonly vol: Range;
|
|
592
|
+
readonly wet?: number;
|
|
593
|
+
readonly delay?: Range;
|
|
594
|
+
};
|
|
595
|
+
/** `sNoise` (cena L1323): noise from a random point, a filter (default lowpass, Q 0.7), the gain rising over `a` and falling over `d`. */
|
|
596
|
+
declare function sceneNoise(o: SceneNoise): Layer;
|
|
597
|
+
type SceneTone = {
|
|
598
|
+
readonly wave?: OscillatorType;
|
|
599
|
+
readonly f: Range;
|
|
600
|
+
readonly f2?: number;
|
|
601
|
+
readonly d: SceneLength;
|
|
602
|
+
readonly vol: Range;
|
|
603
|
+
readonly wet?: number;
|
|
604
|
+
readonly delay?: Range;
|
|
605
|
+
};
|
|
606
|
+
/** `sTone` (cena L1324): an oscillator sliding from `f` to `f2` (back to `f` when none), the gain rising over 0.002 s and falling over `d`. */
|
|
607
|
+
declare function sceneTone(o: SceneTone): Layer;
|
|
608
|
+
type SceneVoice = {
|
|
609
|
+
/** Start and end frequency of the sweep (the page ramps linearly from `f0` to `f1` over the length). */
|
|
610
|
+
readonly f0: Range;
|
|
611
|
+
readonly f1: Range;
|
|
612
|
+
/** Length in seconds. */
|
|
613
|
+
readonly d: SceneLength;
|
|
614
|
+
/** Centre of the first and second formant (Q 4 and 6) and of the breath band (Q 1.1). */
|
|
615
|
+
readonly fA: Range;
|
|
616
|
+
readonly fB: Range;
|
|
617
|
+
/** Level of the breath noise. */
|
|
618
|
+
readonly noise: Range;
|
|
619
|
+
readonly vol: Range;
|
|
620
|
+
readonly wave?: OscillatorType;
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* `voice` (cena L1325): an oscillator swept from `f0` to `f1` with a vibrato of 4 to 10 Hz (depth 0.08 of `f0`) through two formant bands plus a band of
|
|
624
|
+
* breath noise, summed and boosted 3.2 times, the gain rising over 0.1 s, held to 0.6 of the length and falling; reverb send 0.45. It is the one
|
|
625
|
+
* layer the page builds with `voice(...)`, so a scream is two of them.
|
|
626
|
+
*/
|
|
627
|
+
declare function sceneVoice(o: SceneVoice): Layer;
|
|
628
|
+
/** The layer played `count` times (a team `for` loop whose body draws numbers): each pass is its own layer, so the draws stay in the loop's order. */
|
|
629
|
+
declare const sceneMany: (count: number, make: () => Layer) => Layer[];
|
|
630
|
+
type SceneRecipeOptions = {
|
|
631
|
+
/** Named numbers drawn first (`PlayOptions.params` can override them). */
|
|
632
|
+
readonly vars?: SoundRecipe['vars'];
|
|
633
|
+
readonly bus?: BusId;
|
|
634
|
+
readonly group?: string;
|
|
635
|
+
};
|
|
636
|
+
/** A recipe in the art scene's mix: positional with the art scene's curve (`vol / (1 + d² × 0.008)`) and pan; `line` is the team code it comes from (`cena:<line>`). */
|
|
637
|
+
declare function sceneRecipe(id: string, layers: readonly Layer[], line: string, options?: SceneRecipeOptions): SoundRecipe;
|
|
638
|
+
|
|
581
639
|
declare const LIBRARY_SOUNDS: readonly SoundRecipe[];
|
|
582
640
|
declare const LIBRARY_LOOPS: readonly LoopRecipe[];
|
|
583
641
|
declare const LIBRARY_BANK: SoundBank;
|
|
@@ -780,4 +838,4 @@ type AudioBenchText = {
|
|
|
780
838
|
};
|
|
781
839
|
declare const audioBenchTextOf: (locale: string) => AudioBenchText;
|
|
782
840
|
|
|
783
|
-
export { type AudioBenchText, type AudioChannel, type AudioEngine, type AudioEngineOptions, type AudioLoopHandle, type AudioScenarioLike, type AudioStats, type AudioTestModule, type AudioTestModuleOptions, type Automation, BENCH_BANK, BUILD_ORDERS, BUS_IDS, BUS_PRIORITY, type BuildOrder, type BusId, type BusSetting, type Curve, DEFAULT_LISTENER, DEFAULT_MAX_VOICES, DEFAULT_MIX, DISTANCE_CURVES, type DeepPartial, type DistanceCurveName, type DistanceMode, type EngineSink, type Envelope, type EnvelopePoint, type Falloff, type FilterBank, type FilterSpec, type FormantVoiceSource, GAME_SFX_BANK, GAME_SFX_CLASSES, GAME_SFX_FALLBACK, LIBRARY_BANK, LIBRARY_LOOPS, LIBRARY_SOUNDS, LOOP_SMOOTHING, LOOP_START_ABOVE, type Layer, type LayerSource, type Listener, type LoopAction, type LoopOrder, type LoopRecipe, type LoopState, MIX_PROFILES, type MixChain, type MixProfile, type Modulator, type NoiseClass, type NoiseSource, type OscSource, PAN_LIMIT, PAN_MIN_DISTANCE, type ParamRef, type PlanInput, type PlannedAutomation, type PlannedEnvelopePoint, type PlannedFilter, type PlannedLayer, type PlannedModulator, type PlannedSource, type PlayOptions, type Range, type RealizeTarget, type RealizedVoice, type SoundBank, type SoundPlan, type SoundRecipe, type SoundRegistry, type Time, VOICE_GROUPS, type VoiceDecision, type VoiceGroup, type VoiceHandle, type VoiceManager, type VoiceManagerOptions, type VoiceRefusal, type VoiceRequest, type VoiceStats, audioBenchTextOf, bufferOf, buildChain, busGain, busPriority, createAudioEngine, createAudioSink, createAudioTestModule, createLoopState, createRng, createSoundRegistry, createVoiceManager, cutoffDivisor, drawBetween, falloffGain, fillImpulse, fillNoise, globalContext, isSilent, listenerDistance, noiseClassRecipe, panFor, planSound, realize, resolveMix, streamFor, streamSeed, unlockContext };
|
|
841
|
+
export { type AudioBenchText, type AudioChannel, type AudioEngine, type AudioEngineOptions, type AudioLoopHandle, type AudioScenarioLike, type AudioStats, type AudioTestModule, type AudioTestModuleOptions, type Automation, BENCH_BANK, BUILD_ORDERS, BUS_IDS, BUS_PRIORITY, type BuildOrder, type BusId, type BusSetting, type Curve, DEFAULT_LISTENER, DEFAULT_MAX_VOICES, DEFAULT_MIX, DISTANCE_CURVES, type DeepPartial, type DistanceCurveName, type DistanceMode, type EngineSink, type Envelope, type EnvelopePoint, type Falloff, type FilterBank, type FilterSpec, type FormantVoiceSource, GAME_SFX_BANK, GAME_SFX_CLASSES, GAME_SFX_FALLBACK, LIBRARY_BANK, LIBRARY_LOOPS, LIBRARY_SOUNDS, LOOP_SMOOTHING, LOOP_START_ABOVE, type Layer, type LayerSource, type Listener, type LoopAction, type LoopOrder, type LoopRecipe, type LoopState, MIX_PROFILES, type MixChain, type MixProfile, type Modulator, type NoiseClass, type NoiseSource, type OscSource, PAN_LIMIT, PAN_MIN_DISTANCE, type ParamRef, type PlanInput, type PlannedAutomation, type PlannedEnvelopePoint, type PlannedFilter, type PlannedLayer, type PlannedModulator, type PlannedSource, type PlayOptions, type Range, type RealizeTarget, type RealizedVoice, type SceneLength, type SceneNoise, type SceneRecipeOptions, type SceneTone, type SceneVoice, type SoundBank, type SoundPlan, type SoundRecipe, type SoundRegistry, type Time, VOICE_GROUPS, type VoiceDecision, type VoiceGroup, type VoiceHandle, type VoiceManager, type VoiceManagerOptions, type VoiceRefusal, type VoiceRequest, type VoiceStats, audioBenchTextOf, bufferOf, buildChain, busGain, busPriority, createAudioEngine, createAudioSink, createAudioTestModule, createLoopState, createRng, createSoundRegistry, createVoiceManager, cutoffDivisor, drawBetween, falloffGain, fillImpulse, fillNoise, globalContext, isSilent, listenerDistance, noiseClassRecipe, panFor, planSound, realize, resolveMix, sceneMany, sceneNoise, sceneRecipe, sceneTone, sceneVoice, streamFor, streamSeed, unlockContext };
|
package/dist/index.global.js
CHANGED
|
@@ -66,6 +66,11 @@ var GameAudio = (() => {
|
|
|
66
66
|
planSound: () => planSound,
|
|
67
67
|
realize: () => realize,
|
|
68
68
|
resolveMix: () => resolveMix,
|
|
69
|
+
sceneMany: () => sceneMany,
|
|
70
|
+
sceneNoise: () => sceneNoise,
|
|
71
|
+
sceneRecipe: () => sceneRecipe,
|
|
72
|
+
sceneTone: () => sceneTone,
|
|
73
|
+
sceneVoice: () => sceneVoice,
|
|
69
74
|
streamFor: () => streamFor,
|
|
70
75
|
streamSeed: () => streamSeed,
|
|
71
76
|
unlockContext: () => unlockContext
|
|
@@ -356,8 +361,8 @@ var GameAudio = (() => {
|
|
|
356
361
|
const depth = typeof modulator.depth === "object" && "scaleOf" in modulator.depth ? sourceFrequency * modulator.depth.factor : pending.depth ?? 0;
|
|
357
362
|
return { target: modulator.target, ...modulator.wave ? { wave: modulator.wave } : {}, rate: pending.rate, depth, ...modulator.base === void 0 ? {} : { base: modulator.base } };
|
|
358
363
|
}
|
|
359
|
-
function finishEnvelope(
|
|
360
|
-
return
|
|
364
|
+
function finishEnvelope(envelope2, duration, level) {
|
|
365
|
+
return envelope2.map(([time, value, curve, fixed2]) => ({ time: timeOf(time, duration), value: fixed2 === "fixed" ? value : value * level, curve }));
|
|
361
366
|
}
|
|
362
367
|
function planLayer(layer, scope, defaults, levelInEnvelope, soundLevel) {
|
|
363
368
|
const pending = drawLayer(layer, scope);
|
|
@@ -596,6 +601,83 @@ var GameAudio = (() => {
|
|
|
596
601
|
}
|
|
597
602
|
var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
|
|
598
603
|
|
|
604
|
+
// src/recipes/scene.ts
|
|
605
|
+
var ATTACK = 2e-3;
|
|
606
|
+
function lengthOf(d, attack) {
|
|
607
|
+
if (typeof d === "number") return attack + d;
|
|
608
|
+
if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
|
|
609
|
+
return { ...d, add: (d.add ?? 0) + attack };
|
|
610
|
+
}
|
|
611
|
+
var envelope = (attack) => [
|
|
612
|
+
[0, 1e-4, "set", "fixed"],
|
|
613
|
+
[attack, 1, "exp"],
|
|
614
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
615
|
+
];
|
|
616
|
+
function sceneNoise(o) {
|
|
617
|
+
const a = o.a ?? ATTACK;
|
|
618
|
+
const over = typeof o.d === "number" ? o.d : 0;
|
|
619
|
+
return {
|
|
620
|
+
filters: {
|
|
621
|
+
mode: "serial",
|
|
622
|
+
bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
|
|
623
|
+
},
|
|
624
|
+
duration: lengthOf(o.d, a),
|
|
625
|
+
gain: o.vol,
|
|
626
|
+
...o.delay === void 0 ? {} : { at: o.delay },
|
|
627
|
+
...o.wet === void 0 ? {} : { wet: o.wet },
|
|
628
|
+
source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
|
|
629
|
+
envelope: envelope(a)
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function sceneTone(o) {
|
|
633
|
+
return {
|
|
634
|
+
source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
|
|
635
|
+
duration: lengthOf(o.d, ATTACK),
|
|
636
|
+
gain: o.vol,
|
|
637
|
+
...o.delay === void 0 ? {} : { at: o.delay },
|
|
638
|
+
...o.wet === void 0 ? {} : { wet: o.wet },
|
|
639
|
+
envelope: envelope(ATTACK)
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function sceneVoice(o) {
|
|
643
|
+
return {
|
|
644
|
+
duration: lengthOf(o.d, 0),
|
|
645
|
+
gain: o.vol,
|
|
646
|
+
wet: 0.45,
|
|
647
|
+
source: {
|
|
648
|
+
kind: "formant_voice",
|
|
649
|
+
wave: o.wave ?? "sawtooth",
|
|
650
|
+
frequency: { from: o.f0, to: o.f1, curve: "linear", over: "duration" },
|
|
651
|
+
vibrato: { rate: [4, 10], depth: 0.08 },
|
|
652
|
+
formants: [
|
|
653
|
+
{ frequency: o.fA, q: 4 },
|
|
654
|
+
{ frequency: o.fB, q: 6 }
|
|
655
|
+
],
|
|
656
|
+
breath: { frequency: o.fB, q: 1.1, gain: o.noise },
|
|
657
|
+
mix: 3.2
|
|
658
|
+
},
|
|
659
|
+
envelope: [
|
|
660
|
+
[0, 1e-4, "set", "fixed"],
|
|
661
|
+
[0.1, 1, "exp"],
|
|
662
|
+
["0.6d", 1, "set"],
|
|
663
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
664
|
+
]
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
var sceneMany = (count, make) => Array.from({ length: count }, make);
|
|
668
|
+
function sceneRecipe(id, layers, line, options = {}) {
|
|
669
|
+
return {
|
|
670
|
+
id,
|
|
671
|
+
bus: options.bus ?? "world",
|
|
672
|
+
pan: true,
|
|
673
|
+
layers,
|
|
674
|
+
falloff: DISTANCE_CURVES.art_scene,
|
|
675
|
+
...options.vars ? { vars: options.vars } : {},
|
|
676
|
+
...options.group ? { group: options.group } : {},
|
|
677
|
+
sources: [`cena:${line}`]
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
599
681
|
// src/library/crowd_rumble.json
|
|
600
682
|
var crowd_rumble_default = {
|
|
601
683
|
id: "crowd_rumble",
|
|
@@ -1149,10 +1231,10 @@ var GameAudio = (() => {
|
|
|
1149
1231
|
assign(filter.frequency, band.frequency, time);
|
|
1150
1232
|
if (band.q !== void 0) filter.Q.value = band.q;
|
|
1151
1233
|
}
|
|
1152
|
-
const
|
|
1153
|
-
applyEnvelope(
|
|
1154
|
-
oscillator.connect(
|
|
1155
|
-
|
|
1234
|
+
const envelope2 = context.createGain();
|
|
1235
|
+
applyEnvelope(envelope2.gain, layer.envelope, time);
|
|
1236
|
+
oscillator.connect(envelope2);
|
|
1237
|
+
envelope2.connect(filter);
|
|
1156
1238
|
oscillator.start(time);
|
|
1157
1239
|
oscillator.stop(end(layer, time));
|
|
1158
1240
|
return oscillator;
|
|
@@ -1189,12 +1271,12 @@ var GameAudio = (() => {
|
|
|
1189
1271
|
rampTo(node.frequency, filter.frequency, time);
|
|
1190
1272
|
return node;
|
|
1191
1273
|
});
|
|
1192
|
-
const
|
|
1193
|
-
applyEnvelope(
|
|
1274
|
+
const envelope2 = context.createGain();
|
|
1275
|
+
applyEnvelope(envelope2.gain, layer.envelope, time);
|
|
1194
1276
|
let tail = source;
|
|
1195
1277
|
for (const filter of filters) tail = tail.connect(filter);
|
|
1196
|
-
tail.connect(
|
|
1197
|
-
|
|
1278
|
+
tail.connect(envelope2);
|
|
1279
|
+
envelope2.connect(outputStage(plan, layer, target));
|
|
1198
1280
|
source.start(time, layer.source.kind === "noise" ? layer.source.offset : 0);
|
|
1199
1281
|
source.stop(end2(layer, time));
|
|
1200
1282
|
return [source];
|
|
@@ -1208,10 +1290,10 @@ var GameAudio = (() => {
|
|
|
1208
1290
|
assign(oscillator.frequency, layer.source.frequency, time);
|
|
1209
1291
|
rampTo(oscillator.frequency, layer.source.frequency, time);
|
|
1210
1292
|
}
|
|
1211
|
-
const
|
|
1212
|
-
applyEnvelope(
|
|
1213
|
-
oscillator.connect(
|
|
1214
|
-
|
|
1293
|
+
const envelope2 = context.createGain();
|
|
1294
|
+
applyEnvelope(envelope2.gain, layer.envelope, time);
|
|
1295
|
+
oscillator.connect(envelope2);
|
|
1296
|
+
envelope2.connect(outputStage(plan, layer, target));
|
|
1215
1297
|
oscillator.start(time);
|
|
1216
1298
|
oscillator.stop(end2(layer, time));
|
|
1217
1299
|
return [oscillator];
|
|
@@ -1240,10 +1322,10 @@ var GameAudio = (() => {
|
|
|
1240
1322
|
breathBand.connect(breathLevel);
|
|
1241
1323
|
for (const formant of formants) formant.connect(mix);
|
|
1242
1324
|
breathLevel.connect(mix);
|
|
1243
|
-
const
|
|
1244
|
-
applyEnvelope(
|
|
1245
|
-
mix.connect(
|
|
1246
|
-
|
|
1325
|
+
const envelope2 = context.createGain();
|
|
1326
|
+
applyEnvelope(envelope2.gain, layer.envelope, time);
|
|
1327
|
+
mix.connect(envelope2);
|
|
1328
|
+
envelope2.connect(outputStage(plan, layer, target));
|
|
1247
1329
|
const stop = end2(layer, time);
|
|
1248
1330
|
oscillator.start(time);
|
|
1249
1331
|
vibrato.start(time);
|
|
@@ -1281,17 +1363,17 @@ var GameAudio = (() => {
|
|
|
1281
1363
|
return { lfo, depth, target: modulator.target };
|
|
1282
1364
|
});
|
|
1283
1365
|
const lfos = depths.map(({ lfo }) => lfo);
|
|
1284
|
-
const
|
|
1285
|
-
applyEnvelope(
|
|
1286
|
-
for (const { depth, target: modulated } of depths) if (modulated === "gain") depth.connect(
|
|
1366
|
+
const envelope2 = context.createGain();
|
|
1367
|
+
applyEnvelope(envelope2.gain, layer.envelope, time);
|
|
1368
|
+
for (const { depth, target: modulated } of depths) if (modulated === "gain") depth.connect(envelope2.gain);
|
|
1287
1369
|
const filters = (layer.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
|
|
1288
1370
|
let tail = oscillator;
|
|
1289
1371
|
for (const filter of filters) tail = tail.connect(filter);
|
|
1290
|
-
tail.connect(
|
|
1291
|
-
|
|
1372
|
+
tail.connect(envelope2);
|
|
1373
|
+
envelope2.connect(target.output);
|
|
1292
1374
|
if (layer.wet && target.wet) {
|
|
1293
1375
|
const send = gainNode(context, layer.wet);
|
|
1294
|
-
|
|
1376
|
+
envelope2.connect(send);
|
|
1295
1377
|
send.connect(target.wet);
|
|
1296
1378
|
}
|
|
1297
1379
|
const stop = end2(layer, time);
|
|
@@ -1307,11 +1389,11 @@ var GameAudio = (() => {
|
|
|
1307
1389
|
const { context } = target;
|
|
1308
1390
|
const source = noiseSource(target, layer.source.kind === "noise" && layer.source.loop);
|
|
1309
1391
|
const filters = (layer.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
|
|
1310
|
-
const
|
|
1311
|
-
applyEnvelope(
|
|
1392
|
+
const envelope2 = context.createGain();
|
|
1393
|
+
applyEnvelope(envelope2.gain, layer.envelope, time);
|
|
1312
1394
|
let tail = source;
|
|
1313
1395
|
for (const filter of filters) tail = tail.connect(filter);
|
|
1314
|
-
tail.connect(
|
|
1396
|
+
tail.connect(envelope2).connect(target.output);
|
|
1315
1397
|
source.start(time, layer.source.kind === "noise" ? layer.source.offset : 0);
|
|
1316
1398
|
source.stop(time + layer.duration + layer.tail);
|
|
1317
1399
|
return source;
|
|
@@ -1324,9 +1406,9 @@ var GameAudio = (() => {
|
|
|
1324
1406
|
assign(oscillator.frequency, layer.source.frequency, time);
|
|
1325
1407
|
rampTo(oscillator.frequency, layer.source.frequency, time);
|
|
1326
1408
|
}
|
|
1327
|
-
const
|
|
1328
|
-
applyEnvelope(
|
|
1329
|
-
oscillator.connect(
|
|
1409
|
+
const envelope2 = context.createGain();
|
|
1410
|
+
applyEnvelope(envelope2.gain, layer.envelope, time);
|
|
1411
|
+
oscillator.connect(envelope2).connect(target.output);
|
|
1330
1412
|
oscillator.start(time);
|
|
1331
1413
|
oscillator.stop(time + layer.duration + layer.tail);
|
|
1332
1414
|
return oscillator;
|
|
@@ -1883,8 +1965,8 @@ var GameAudio = (() => {
|
|
|
1883
1965
|
const layers2 = planned.layers.map((layer, index) => {
|
|
1884
1966
|
const source = layer.source.kind === "noise" ? `noise@${layer.source.offset.toFixed(4)}` : layer.source.kind === "osc" ? `${layer.source.wave}@${layer.source.frequency.value ?? layer.source.frequency.from}` : layer.source.kind;
|
|
1885
1967
|
const filters = (layer.filters?.bank ?? []).map((filter) => `${filter.type}@${(filter.frequency.value ?? filter.frequency.from ?? 0).toFixed(1)}`).join(" ");
|
|
1886
|
-
const
|
|
1887
|
-
return ` ${text.sheet.layer} ${index + 1}: ${source} at ${layer.at.toFixed(3)} for ${layer.duration.toFixed(3)} ${filters} [${
|
|
1968
|
+
const envelope2 = layer.envelope.map((point) => `${point.time.toFixed(3)}:${point.value.toPrecision(3)}`).join(" ");
|
|
1969
|
+
return ` ${text.sheet.layer} ${index + 1}: ${source} at ${layer.at.toFixed(3)} for ${layer.duration.toFixed(3)} ${filters} [${envelope2}]`;
|
|
1888
1970
|
});
|
|
1889
1971
|
return [head, ...layers2].join("\n");
|
|
1890
1972
|
};
|