@opendaw/studio-core 0.0.42 → 0.0.44
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/AssetService.d.ts +28 -0
- package/dist/AssetService.d.ts.map +1 -0
- package/dist/AssetService.js +94 -0
- package/dist/EngineWorklet.d.ts +1 -1
- package/dist/EngineWorklet.d.ts.map +1 -1
- package/dist/capture/CaptureDevices.js +1 -1
- package/dist/cloud/CloudAuthManager.d.ts +6 -2
- package/dist/cloud/CloudAuthManager.d.ts.map +1 -1
- package/dist/cloud/CloudAuthManager.js +12 -11
- package/dist/cloud/CloudBackup.js +1 -1
- package/dist/cloud/{CloudBackupSoundfont.d.ts → CloudBackupSoundfonts.d.ts} +1 -1
- package/dist/cloud/CloudBackupSoundfonts.d.ts.map +1 -0
- package/dist/cloud/{CloudBackupSoundfont.js → CloudBackupSoundfonts.js} +1 -1
- package/dist/dawproject/DawProjectService.js +1 -1
- package/dist/processors.js +27 -5
- package/dist/processors.js.map +4 -4
- package/dist/project/Project.d.ts +2 -2
- package/dist/project/Project.d.ts.map +1 -1
- package/dist/project/Project.js +2 -3
- package/dist/samples/OpenSampleAPI.d.ts.map +1 -1
- package/dist/samples/OpenSampleAPI.js +5 -3
- package/dist/samples/SampleService.d.ts +10 -13
- package/dist/samples/SampleService.d.ts.map +1 -1
- package/dist/samples/SampleService.js +20 -43
- package/dist/samples/SampleStorage.d.ts +1 -1
- package/dist/samples/SampleStorage.d.ts.map +1 -1
- package/dist/samples/SampleStorage.js +1 -1
- package/dist/soundfont/DefaultSoundfontLoader.d.ts +2 -1
- package/dist/soundfont/DefaultSoundfontLoader.d.ts.map +1 -1
- package/dist/soundfont/DefaultSoundfontLoader.js +14 -4
- package/dist/soundfont/DefaultSoundfontLoaderManager.d.ts +2 -1
- package/dist/soundfont/DefaultSoundfontLoaderManager.d.ts.map +1 -1
- package/dist/soundfont/DefaultSoundfontLoaderManager.js +1 -0
- package/dist/soundfont/OpenSoundfontAPI.d.ts.map +1 -1
- package/dist/soundfont/OpenSoundfontAPI.js +6 -4
- package/dist/soundfont/SoundfontService.d.ts +10 -13
- package/dist/soundfont/SoundfontService.d.ts.map +1 -1
- package/dist/soundfont/SoundfontService.js +33 -52
- package/dist/workers-main.js +2 -2
- package/dist/workers-main.js.map +3 -3
- package/dist/ysync/YService.d.ts.map +1 -1
- package/dist/ysync/YService.js +4 -3
- package/dist/ysync/YSync.d.ts.map +1 -1
- package/dist/ysync/YSync.js +79 -51
- package/package.json +17 -16
- package/dist/cloud/CloudBackupSoundfont.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YService.d.ts","sourceRoot":"","sources":["../../src/ysync/YService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAmC,MAAM,kBAAkB,CAAA;AAQjF,OAAO,EAAC,OAAO,EAAE,UAAU,EAAC,MAAM,YAAY,CAAA;AAI9C,yBAAiB,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"YService.d.ts","sourceRoot":"","sources":["../../src/ysync/YService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAmC,MAAM,kBAAkB,CAAA;AAQjF,OAAO,EAAC,OAAO,EAAE,UAAU,EAAC,MAAM,YAAY,CAAA;AAI9C,yBAAiB,QAAQ,CAAC;IAMf,MAAM,eAAe,GAAU,YAAY,MAAM,CAAC,OAAO,CAAC,EAC3B,KAAK,UAAU,EACf,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CAiDvE,CAAA;CACJ"}
|
package/dist/ysync/YService.js
CHANGED
|
@@ -10,9 +10,10 @@ import { Project } from "../project";
|
|
|
10
10
|
// https://inspector.yjs.dev/
|
|
11
11
|
export var YService;
|
|
12
12
|
(function (YService) {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
const USE_LOCAL_SERVER = false;
|
|
14
|
+
const LOCAL_SERVER_URL = "wss://localhost:1234";
|
|
15
|
+
const ONLINE_SERVER_URL = "wss://live.opendaw.studio";
|
|
16
|
+
const serverUrl = USE_LOCAL_SERVER ? LOCAL_SERVER_URL : ONLINE_SERVER_URL;
|
|
16
17
|
YService.getOrCreateRoom = async (optProject, env, roomName) => {
|
|
17
18
|
if (roomName === "signaling") {
|
|
18
19
|
return panic("Invalid room name: signaling");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YSync.d.ts","sourceRoot":"","sources":["../../src/ysync/YSync.ts"],"names":[],"mappings":"AAAA,OAAO,EASH,QAAQ,EAER,UAAU,EAGb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAa,QAAQ,EAA2D,MAAM,kBAAkB,CAAA;AAE/G,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAIxB,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtB,GAAG,EAAE,CAAC,CAAC,GAAG,CAAA;IACV,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAC/B,CAAA;AAED,qBAAa,KAAK,CAAC,CAAC,CAAE,YAAW,UAAU;;IACvC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,OAAO;WAItB,QAAQ,CAAC,CAAC,EAAE,EAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;WAa7D,IAAI,CAAC,CAAC,EAAE,EAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBA4B1D,EAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IASnD,SAAS,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"YSync.d.ts","sourceRoot":"","sources":["../../src/ysync/YSync.ts"],"names":[],"mappings":"AAAA,OAAO,EASH,QAAQ,EAER,UAAU,EAGb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAa,QAAQ,EAA2D,MAAM,kBAAkB,CAAA;AAE/G,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAIxB,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtB,GAAG,EAAE,CAAC,CAAC,GAAG,CAAA;IACV,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAC/B,CAAA;AAED,qBAAa,KAAK,CAAC,CAAC,CAAE,YAAW,UAAU;;IACvC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,OAAO;WAItB,QAAQ,CAAC,CAAC,EAAE,EAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;WAa7D,IAAI,CAAC,CAAC,EAAE,EAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBA4B1D,EAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IASnD,SAAS,IAAI,IAAI;CA0KpB"}
|
package/dist/ysync/YSync.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asDefined, assert, EmptyExec,
|
|
1
|
+
import { asDefined, asInstanceOf, assert, EmptyExec, isUndefined, Option, panic, Terminable, Terminator, UUID } from "@opendaw/lib-std";
|
|
2
2
|
import { YMapper } from "./YMapper";
|
|
3
3
|
import * as Y from "yjs";
|
|
4
4
|
export class YSync {
|
|
@@ -14,7 +14,7 @@ export class YSync {
|
|
|
14
14
|
const key = UUID.toString(box.address.uuid);
|
|
15
15
|
const map = YMapper.createBoxMap(box);
|
|
16
16
|
boxesMap.set(key, map);
|
|
17
|
-
}), "populate");
|
|
17
|
+
}), "[openDAW] populate");
|
|
18
18
|
return sync;
|
|
19
19
|
}
|
|
20
20
|
static async join({ boxGraph, doc }) {
|
|
@@ -51,47 +51,44 @@ export class YSync {
|
|
|
51
51
|
}
|
|
52
52
|
terminate() { this.#terminator.terminate(); }
|
|
53
53
|
#setupYjs() {
|
|
54
|
-
const eventHandler = (events,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
const eventHandler = (events, { origin, local }) => {
|
|
55
|
+
const originLabel = typeof origin === "string" ? origin : "WebsocketProvider";
|
|
56
|
+
console.debug(`got ${events.length} ${local ? "local" : "external"} updates from '${originLabel}'`);
|
|
57
|
+
if (local) {
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
this.#boxGraph.beginTransaction();
|
|
61
|
-
|
|
61
|
+
for (const event of events) {
|
|
62
62
|
const path = event.path;
|
|
63
63
|
const keys = event.changes.keys;
|
|
64
|
-
|
|
64
|
+
for (const [key, change] of keys.entries()) {
|
|
65
65
|
if (change.action === "add") {
|
|
66
66
|
assert(path.length === 0, "'Add' cannot have a path");
|
|
67
|
-
|
|
68
|
-
const name = map.get("name");
|
|
69
|
-
const fields = map.get("fields");
|
|
70
|
-
const uuid = UUID.parse(key);
|
|
71
|
-
this.#boxGraph.createBox(name, uuid, box => YMapper.applyFromBoxMap(box, fields));
|
|
67
|
+
this.#createBox(key);
|
|
72
68
|
}
|
|
73
69
|
else if (change.action === "update") {
|
|
74
70
|
if (path.length === 0) {
|
|
75
|
-
|
|
71
|
+
continue;
|
|
76
72
|
}
|
|
77
73
|
assert(path.length >= 2, "Invalid path: must have at least 2 elements (uuid, 'fields').");
|
|
78
74
|
this.#updateValue(path, key);
|
|
79
75
|
}
|
|
80
76
|
else if (change.action === "delete") {
|
|
81
77
|
assert(path.length === 0, "'Delete' cannot have a path");
|
|
82
|
-
|
|
83
|
-
.unwrap("Could not find box to delete");
|
|
84
|
-
// It is possible that Yjs have swallowed the pointer updates since were are 'inside' the box.
|
|
85
|
-
box.outgoingEdges().forEach(([pointer]) => pointer.defer());
|
|
86
|
-
box.incomingEdges().forEach(pointer => pointer.defer());
|
|
87
|
-
this.#boxGraph.unstageBox(box);
|
|
78
|
+
this.#deleteBox(key);
|
|
88
79
|
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
this.#ignoreUpdates = true;
|
|
84
|
+
this.#boxGraph.endTransaction();
|
|
85
|
+
this.#ignoreUpdates = false;
|
|
86
|
+
this.#boxGraph.verifyPointers();
|
|
87
|
+
}
|
|
88
|
+
catch (reason) {
|
|
89
|
+
this.terminate();
|
|
90
|
+
return panic(reason);
|
|
91
|
+
}
|
|
95
92
|
const highLevelConflict = this.#conflict.mapOr(check => check(), false);
|
|
96
93
|
if (highLevelConflict) {
|
|
97
94
|
this.#rollbackTransaction(events);
|
|
@@ -100,13 +97,31 @@ export class YSync {
|
|
|
100
97
|
this.#boxesMap.observeDeep(eventHandler);
|
|
101
98
|
return { terminate: () => { this.#boxesMap.unobserveDeep(eventHandler); } };
|
|
102
99
|
}
|
|
100
|
+
#createBox(key) {
|
|
101
|
+
const map = this.#boxesMap.get(key);
|
|
102
|
+
const name = map.get("name");
|
|
103
|
+
const fields = map.get("fields");
|
|
104
|
+
const uuid = UUID.parse(key);
|
|
105
|
+
const optBox = this.#boxGraph.findBox(UUID.parse(key));
|
|
106
|
+
if (optBox.isEmpty()) {
|
|
107
|
+
this.#boxGraph.createBox(name, uuid, box => YMapper.applyFromBoxMap(box, fields));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.debug(`Box '${key}' has already been created. Performing 'Upsert'.`);
|
|
111
|
+
YMapper.applyFromBoxMap(optBox.unwrap(), fields);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
103
114
|
#updateValue(path, key) {
|
|
115
|
+
const vertexOption = this.#boxGraph.findVertex(YMapper.pathToAddress(path, key));
|
|
116
|
+
if (vertexOption.isEmpty()) {
|
|
117
|
+
console.debug(`Vertex at '${path}' does not exist. Ignoring.`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const vertex = vertexOption.unwrap("Could not find field");
|
|
104
121
|
const [uuidAsString, fieldsKey, ...fieldKeys] = path;
|
|
105
122
|
const targetMap = YMapper.findMap(this.#boxesMap
|
|
106
123
|
.get(String(uuidAsString))
|
|
107
124
|
.get(String(fieldsKey)), fieldKeys);
|
|
108
|
-
const vertexOption = this.#boxGraph.findVertex(YMapper.pathToAddress(path, key));
|
|
109
|
-
const vertex = vertexOption.unwrap("Could not find field");
|
|
110
125
|
assert(vertex.isField(), "Vertex must be either Primitive or Pointer");
|
|
111
126
|
vertex.accept({
|
|
112
127
|
visitField: (_) => panic("Vertex must be either Primitive or Pointer"),
|
|
@@ -116,33 +131,46 @@ export class YSync {
|
|
|
116
131
|
visitPrimitiveField: (field) => field.fromJSON(targetMap.get(key))
|
|
117
132
|
});
|
|
118
133
|
}
|
|
134
|
+
#deleteBox(key) {
|
|
135
|
+
const optBox = this.#boxGraph.findBox(UUID.parse(key));
|
|
136
|
+
if (optBox.isEmpty()) {
|
|
137
|
+
console.debug(`Box '${key}' has already been deleted. Ignoring.`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const box = optBox.unwrap();
|
|
141
|
+
// It is possible that Yjs have swallowed the pointer releases since they were 'inside' the box.
|
|
142
|
+
box.outgoingEdges().forEach(([pointer]) => pointer.defer());
|
|
143
|
+
box.incomingEdges().forEach(pointer => pointer.defer());
|
|
144
|
+
this.#boxGraph.unstageBox(box);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
119
147
|
#rollbackTransaction(events) {
|
|
120
|
-
console.debug(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.forEach(([key, change]) => {
|
|
130
|
-
if (change.action === "add") {
|
|
131
|
-
target.delete(key);
|
|
132
|
-
}
|
|
133
|
-
else if (change.action === "update") {
|
|
134
|
-
if (isUndefined(change.oldValue)) {
|
|
148
|
+
console.debug(`rollback ${events.length} events...`);
|
|
149
|
+
this.#doc.transact(() => {
|
|
150
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
151
|
+
const event = events[i];
|
|
152
|
+
const target = asInstanceOf(event.target, Y.Map);
|
|
153
|
+
Array.from(event.changes.keys.entries())
|
|
154
|
+
.toReversed()
|
|
155
|
+
.forEach(([key, change]) => {
|
|
156
|
+
if (change.action === "add") {
|
|
135
157
|
target.delete(key);
|
|
136
158
|
}
|
|
137
|
-
else {
|
|
159
|
+
else if (change.action === "update") {
|
|
160
|
+
if (isUndefined(change.oldValue)) {
|
|
161
|
+
console.warn(`oldValue of ${change} is undefined`);
|
|
162
|
+
target.delete(key);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
target.set(key, change.oldValue);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (change.action === "delete") {
|
|
138
169
|
target.set(key, change.oldValue);
|
|
139
170
|
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}, "[openDAW] rollback");
|
|
146
174
|
}
|
|
147
175
|
#setupOpenDAW() {
|
|
148
176
|
return Terminable.many(this.#boxGraph.subscribeTransaction({
|
|
@@ -185,7 +213,7 @@ export class YSync {
|
|
|
185
213
|
else if (update.type === "delete") {
|
|
186
214
|
this.#boxesMap.delete(UUID.toString(update.uuid));
|
|
187
215
|
}
|
|
188
|
-
}), "openDAW");
|
|
216
|
+
}), "[openDAW] updates");
|
|
189
217
|
this.#updates.length = 0;
|
|
190
218
|
}
|
|
191
219
|
}), this.#boxGraph.subscribeToAllUpdatesImmediate({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendaw/studio-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"license": "LGPL-3.0-or-later",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -26,26 +26,27 @@
|
|
|
26
26
|
"test": "vitest run --config vitest.config.ts"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@opendaw/lib-box": "^0.0.
|
|
30
|
-
"@opendaw/lib-dawproject": "^0.0.
|
|
31
|
-
"@opendaw/lib-dom": "^0.0.
|
|
32
|
-
"@opendaw/lib-dsp": "^0.0.
|
|
33
|
-
"@opendaw/lib-fusion": "^0.0.
|
|
34
|
-
"@opendaw/lib-runtime": "^0.0.
|
|
35
|
-
"@opendaw/lib-std": "^0.0.
|
|
36
|
-
"@opendaw/studio-adapters": "^0.0.
|
|
37
|
-
"@opendaw/studio-boxes": "^0.0.
|
|
38
|
-
"@opendaw/studio-enums": "^0.0.
|
|
29
|
+
"@opendaw/lib-box": "^0.0.35",
|
|
30
|
+
"@opendaw/lib-dawproject": "^0.0.21",
|
|
31
|
+
"@opendaw/lib-dom": "^0.0.35",
|
|
32
|
+
"@opendaw/lib-dsp": "^0.0.35",
|
|
33
|
+
"@opendaw/lib-fusion": "^0.0.35",
|
|
34
|
+
"@opendaw/lib-runtime": "^0.0.35",
|
|
35
|
+
"@opendaw/lib-std": "^0.0.35",
|
|
36
|
+
"@opendaw/studio-adapters": "^0.0.36",
|
|
37
|
+
"@opendaw/studio-boxes": "^0.0.35",
|
|
38
|
+
"@opendaw/studio-enums": "^0.0.26",
|
|
39
39
|
"dropbox": "^10.34.0",
|
|
40
40
|
"y-websocket": "^1.4.5",
|
|
41
|
-
"yjs": "^13.6.27"
|
|
41
|
+
"yjs": "^13.6.27",
|
|
42
|
+
"zod": "^4.1.12"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@opendaw/eslint-config": "^0.0.19",
|
|
45
|
-
"@opendaw/studio-core-processors": "^0.0.
|
|
46
|
-
"@opendaw/studio-core-workers": "^0.0.
|
|
47
|
-
"@opendaw/studio-forge-boxes": "^0.0.
|
|
46
|
+
"@opendaw/studio-core-processors": "^0.0.36",
|
|
47
|
+
"@opendaw/studio-core-workers": "^0.0.31",
|
|
48
|
+
"@opendaw/studio-forge-boxes": "^0.0.35",
|
|
48
49
|
"@opendaw/typescript-config": "^0.0.20"
|
|
49
50
|
},
|
|
50
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "7911c4037c03c3aa6d59cf6790dd86285d58dcf9"
|
|
51
52
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CloudBackupSoundfont.d.ts","sourceRoot":"","sources":["../../src/cloud/CloudBackupSoundfont.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,SAAS,EAAE,QAAQ,EAAmB,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAElG,OAAO,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAA;AAClD,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAK3C,qBAAa,qBAAqB;;IAC9B,MAAM,CAAC,QAAQ,CAAC,UAAU,gBAAe;IACzC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAAkC;IACnE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,GAAI,aAAW,SAAS,EAAE,aAAW,SAAS,aAAY;IAE5F,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM;WAE5B,KAAK,CAAC,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAC1B,GAAG,EAAE,SAAS,CAAC,MAAM,CAAC;IAgBzC,OAAO;CA2GV"}
|