@opendaw/studio-core 0.0.40 → 0.0.41

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.
@@ -1,7 +1,6 @@
1
1
  export * from "./DefaultSampleLoader";
2
2
  export * from "./DefaultSampleLoaderManager";
3
3
  export * from "./OpenSampleAPI";
4
- export * from "./P2PSampleProvider";
5
4
  export * from "./SampleAPI";
6
5
  export * from "./SampleImporter";
7
6
  export * from "./SampleProvider";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/samples/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/samples/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA"}
@@ -1,7 +1,6 @@
1
1
  export * from "./DefaultSampleLoader";
2
2
  export * from "./DefaultSampleLoaderManager";
3
3
  export * from "./OpenSampleAPI";
4
- export * from "./P2PSampleProvider";
5
4
  export * from "./SampleAPI";
6
5
  export * from "./SampleImporter";
7
6
  export * from "./SampleProvider";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendaw/studio-core",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -28,26 +28,26 @@
28
28
  "dependencies": {
29
29
  "@liveblocks/client": "^3.7.1",
30
30
  "@liveblocks/yjs": "^3.7.1",
31
- "@opendaw/lib-box": "^0.0.31",
32
- "@opendaw/lib-dawproject": "^0.0.17",
33
- "@opendaw/lib-dom": "^0.0.31",
34
- "@opendaw/lib-dsp": "^0.0.31",
35
- "@opendaw/lib-fusion": "^0.0.31",
36
- "@opendaw/lib-runtime": "^0.0.31",
37
- "@opendaw/lib-std": "^0.0.31",
38
- "@opendaw/studio-adapters": "^0.0.32",
39
- "@opendaw/studio-boxes": "^0.0.31",
40
- "@opendaw/studio-enums": "^0.0.22",
31
+ "@opendaw/lib-box": "^0.0.32",
32
+ "@opendaw/lib-dawproject": "^0.0.18",
33
+ "@opendaw/lib-dom": "^0.0.32",
34
+ "@opendaw/lib-dsp": "^0.0.32",
35
+ "@opendaw/lib-fusion": "^0.0.32",
36
+ "@opendaw/lib-runtime": "^0.0.32",
37
+ "@opendaw/lib-std": "^0.0.32",
38
+ "@opendaw/studio-adapters": "^0.0.33",
39
+ "@opendaw/studio-boxes": "^0.0.32",
40
+ "@opendaw/studio-enums": "^0.0.23",
41
41
  "dropbox": "^10.34.0",
42
- "y-webrtc": "^10.3.0",
42
+ "y-websocket": "^1.4.5",
43
43
  "yjs": "^13.6.27"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@opendaw/eslint-config": "^0.0.19",
47
- "@opendaw/studio-core-processors": "^0.0.32",
48
- "@opendaw/studio-core-workers": "^0.0.27",
49
- "@opendaw/studio-forge-boxes": "^0.0.31",
47
+ "@opendaw/studio-core-processors": "^0.0.33",
48
+ "@opendaw/studio-core-workers": "^0.0.28",
49
+ "@opendaw/studio-forge-boxes": "^0.0.32",
50
50
  "@opendaw/typescript-config": "^0.0.20"
51
51
  },
52
- "gitHead": "c1718ba63c1f99d28d6a5178c39e96c89b5bb364"
52
+ "gitHead": "705da8c69733c18fbae25c63f87cba8a367b8b38"
53
53
  }
@@ -1,13 +0,0 @@
1
- import { Progress, UUID } from "@opendaw/lib-std";
2
- import { AudioData, SampleMetaData } from "@opendaw/studio-adapters";
3
- import * as Y from "yjs";
4
- import { WebrtcProvider } from "y-webrtc";
5
- import { SampleProvider } from "./SampleProvider";
6
- export declare class P2PSampleProvider implements SampleProvider {
7
- #private;
8
- static create(doc: Y.Doc, provider: WebrtcProvider): P2PSampleProvider;
9
- constructor(doc: Y.Doc, provider: WebrtcProvider);
10
- fetch(uuid: UUID.Bytes, progress: Progress.Handler): Promise<[AudioData, SampleMetaData]>;
11
- share(uuidAsString: string, audioData: AudioData, peaks: ArrayBuffer, metadata: SampleMetaData): Promise<void>;
12
- }
13
- //# sourceMappingURL=P2PSampleProvider.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"P2PSampleProvider.d.ts","sourceRoot":"","sources":["../../src/samples/P2PSampleProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,QAAQ,EAAE,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAC5F,OAAO,EAAC,SAAS,EAAE,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAClE,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AACxB,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAA;AACvC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAkC/C,qBAAa,iBAAkB,YAAW,cAAc;;IACpD,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc,GAAG,iBAAiB;gBAc1D,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc;IAY1C,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAmDzF,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAkSvH"}
@@ -1,350 +0,0 @@
1
- import { asDefined, isDefined, isNotUndefined, panic, UUID } from "@opendaw/lib-std";
2
- import { SampleStorage } from "./SampleStorage";
3
- export class P2PSampleProvider {
4
- static create(doc, provider) {
5
- return new P2PSampleProvider(doc, provider);
6
- }
7
- #doc;
8
- #provider;
9
- #samplesMap;
10
- #CHUNK_SIZE = 16384;
11
- #CHANNEL_LABEL = "openDAW-samples";
12
- #peerChannels = new Map();
13
- #activeTransfers = new Map();
14
- #pendingFetches = new Map();
15
- constructor(doc, provider) {
16
- this.#doc = doc;
17
- this.#provider = provider;
18
- this.#samplesMap = doc.getMap("samples");
19
- console.log(`[P2P] Initializing with provider:`, provider);
20
- console.log(`[P2P] Provider maxConns:`, provider.maxConns);
21
- console.log(`[P2P] Provider filterBcConns:`, provider.filterBcConns);
22
- console.log(`[P2P] Provider room:`, provider.room);
23
- console.log(`[P2P] Provider webrtcConns size:`, provider.webrtcConns?.size);
24
- this.#init();
25
- }
26
- async fetch(uuid, progress) {
27
- const uuidAsString = UUID.toString(uuid);
28
- console.log(`[P2P] Fetching sample: ${uuidAsString}`);
29
- try {
30
- const [audio, _, meta] = await SampleStorage.loadSample(UUID.parse(uuidAsString));
31
- console.log(`[P2P] Sample found in storage: ${uuidAsString}`);
32
- progress(1.0);
33
- return [audio, meta];
34
- }
35
- catch (error) {
36
- console.log(`[P2P] Sample not in storage, requesting from peers: ${uuidAsString}`);
37
- }
38
- const existingPromise = this.#pendingFetches.get(uuidAsString);
39
- if (isNotUndefined(existingPromise)) {
40
- console.log(`[P2P] Sample already being fetched: ${uuidAsString}`);
41
- return existingPromise;
42
- }
43
- this.#requestSampleFromPeers(uuidAsString);
44
- const promise = new Promise((resolve, reject) => {
45
- const timeout = setTimeout(() => {
46
- console.warn(`[P2P] Sample fetch timeout: ${uuidAsString}`);
47
- this.#pendingFetches.delete(uuidAsString);
48
- this.#activeTransfers.delete(uuidAsString);
49
- reject(new Error(`No sample source available for ${uuid}`));
50
- }, 30000);
51
- this.#activeTransfers.set(uuidAsString, {
52
- chunks: [],
53
- expectedSize: 0,
54
- receivedSize: 0,
55
- metadata: {},
56
- resolve,
57
- reject,
58
- progress,
59
- timeout
60
- });
61
- });
62
- this.#pendingFetches.set(uuidAsString, promise);
63
- return promise;
64
- }
65
- #requestSampleFromPeers(uuid) {
66
- console.log(`[P2P] Requesting sample from ${this.#peerChannels.size} peers: ${uuid}`);
67
- this.#peerChannels.forEach(channel => {
68
- if (channel.readyState === "open") {
69
- channel.send(JSON.stringify({
70
- type: "request",
71
- uuid: uuid
72
- }));
73
- }
74
- });
75
- }
76
- async share(uuidAsString, audioData, peaks, metadata) {
77
- console.log(`[P2P] Sharing sample: ${uuidAsString}`);
78
- await SampleStorage.saveSample({
79
- uuid: UUID.parse(uuidAsString),
80
- audio: audioData,
81
- peaks: peaks,
82
- meta: metadata
83
- });
84
- this.#samplesMap.set(uuidAsString, {
85
- uuid: uuidAsString,
86
- ownerId: this.#getMyId(),
87
- metadata,
88
- timestamp: Date.now()
89
- });
90
- const zipData = await this.#createZipFile(audioData, peaks, metadata);
91
- console.log(`[P2P] Broadcasting sample to ${this.#peerChannels.size} peers (${zipData.byteLength} bytes): ${uuidAsString}`);
92
- this.#peerChannels.forEach((channel) => {
93
- if (channel.readyState === "open") {
94
- this.#sendSample(channel, uuidAsString, zipData, metadata);
95
- }
96
- });
97
- }
98
- async #createZipFile(audioData, peaks, metadata) {
99
- const { default: JSZip } = await import("jszip");
100
- const zip = new JSZip();
101
- zip.file("version", "1");
102
- zip.file("metadata.json", JSON.stringify(metadata));
103
- zip.file("audio.bin", this.#audioDataToArrayBuffer(audioData), { binary: true });
104
- zip.file("peaks.bin", peaks, { binary: true });
105
- return await zip.generateAsync({ type: "arraybuffer" });
106
- }
107
- async #extractZipFile(zipBuffer) {
108
- const { default: JSZip } = await import("jszip");
109
- const zip = new JSZip();
110
- await zip.loadAsync(zipBuffer);
111
- const version = await zip.file("version")?.async("string");
112
- if (version !== "1") {
113
- return panic(`Unsupported zip version: ${version}`);
114
- }
115
- const metadataText = await zip.file("metadata.json")?.async("string");
116
- if (!isDefined(metadataText)) {
117
- return panic("Missing metadata.json in zip");
118
- }
119
- const metadata = JSON.parse(metadataText);
120
- const audioBuffer = await zip.file("audio.bin")?.async("arraybuffer");
121
- if (!isDefined(audioBuffer)) {
122
- return panic("Missing audio.bin in zip");
123
- }
124
- const peaks = await zip.file("peaks.bin")?.async("arraybuffer");
125
- if (!isDefined(peaks)) {
126
- return panic("Missing peaks.bin in zip");
127
- }
128
- const audioData = this.#arrayBufferToAudioData(audioBuffer);
129
- return { audioData, peaks, metadata };
130
- }
131
- #init() {
132
- this.#setupPeers();
133
- this.#setupSampleListener();
134
- }
135
- #setupPeers() {
136
- console.log(`[P2P] Setting up peer management`);
137
- this.#provider.on("peers", (event) => {
138
- console.log(`[P2P] Peers event:`, event);
139
- event.added.forEach(peerId => {
140
- console.log(`[P2P] Peer connected: ${peerId}`);
141
- if (event.webrtcPeers.includes(peerId)) {
142
- console.log(`[P2P] Setting up WebRTC data channel for: ${peerId}`);
143
- this.#createChannel(peerId);
144
- this.#setupIncomingChannelHandler(peerId);
145
- }
146
- else if (event.bcPeers.includes(peerId)) {
147
- console.log(`[P2P] Peer is using BroadcastChannel (same device): ${peerId}`);
148
- console.log(`[P2P] Sample sharing not available for BroadcastChannel peers`);
149
- }
150
- });
151
- event.removed.forEach(peerId => {
152
- console.log(`[P2P] Peer disconnected: ${peerId}`);
153
- this.#peerChannels.delete(peerId);
154
- });
155
- console.log(`[P2P] Total WebRTC peers: ${event.webrtcPeers.length}`);
156
- console.log(`[P2P] Total BroadcastChannel peers: ${event.bcPeers.length}`);
157
- console.log(`[P2P] Total active data channels: ${this.#peerChannels.size}`);
158
- });
159
- }
160
- #setupIncomingChannelHandler(peerId) {
161
- const conn = this.#provider.webrtcConns?.get(peerId);
162
- if (!conn?.peer)
163
- return;
164
- conn.peer.ondatachannel = (event) => {
165
- if (event.channel.label === this.#CHANNEL_LABEL) {
166
- console.log(`[P2P] Incoming data channel from: ${peerId}`);
167
- this.#setupChannel(event.channel, peerId);
168
- }
169
- };
170
- }
171
- #createChannel(peerId) {
172
- console.log(`[P2P] Creating data channel for peer: ${peerId}`);
173
- const conn = this.#provider.webrtcConns?.get(peerId);
174
- if (!conn?.peer) {
175
- console.warn(`[P2P] No WebRTC connection found for peer: ${peerId}`);
176
- return;
177
- }
178
- console.log(`[P2P] WebRTC connection state for ${peerId}:`, conn.peer.connectionState);
179
- const channel = conn.peer.createDataChannel(this.#CHANNEL_LABEL, {
180
- ordered: true,
181
- maxRetransmits: 3
182
- });
183
- this.#setupChannel(channel, peerId);
184
- }
185
- #setupChannel(channel, peerId) {
186
- channel.binaryType = "arraybuffer";
187
- channel.onopen = () => {
188
- console.log(`[P2P] Data channel opened: ${peerId}`);
189
- this.#peerChannels.set(peerId, channel);
190
- };
191
- channel.onclose = () => {
192
- console.log(`[P2P] Data channel closed: ${peerId}`);
193
- this.#peerChannels.delete(peerId);
194
- };
195
- channel.onmessage = (event) => this.#handleMessage(event.data, peerId);
196
- }
197
- #setupSampleListener() {
198
- this.#samplesMap.observe((event) => {
199
- event.changes.keys.forEach((change, uuid) => {
200
- if (change.action === "add") {
201
- const sampleRef = this.#samplesMap.get(uuid);
202
- if (sampleRef?.ownerId === this.#getMyId()) {
203
- console.log(`[P2P] Own sample reference added: ${uuid}`);
204
- }
205
- }
206
- });
207
- });
208
- }
209
- #sendSample(channel, uuid, zipData, metadata) {
210
- console.log(`[P2P] Starting sample transfer: ${uuid} (${zipData.byteLength} bytes)`);
211
- channel.send(JSON.stringify({
212
- type: "start",
213
- uuid,
214
- size: zipData.byteLength,
215
- metadata
216
- }));
217
- const chunks = Math.ceil(zipData.byteLength / this.#CHUNK_SIZE);
218
- for (let i = 0; i < chunks; i++) {
219
- const chunk = zipData.slice(i * this.#CHUNK_SIZE, (i + 1) * this.#CHUNK_SIZE);
220
- channel.send(chunk);
221
- }
222
- channel.send(JSON.stringify({ type: "complete", uuid }));
223
- console.log(`[P2P] Sample transfer completed: ${uuid} (${chunks} chunks)`);
224
- }
225
- #handleMessage(data, peerId) {
226
- if (typeof data === "string") {
227
- const msg = JSON.parse(data);
228
- if (msg.type === "start") {
229
- console.log(`[P2P] Receiving sample from ${peerId}: ${msg.uuid} (${msg.size} bytes)`);
230
- const transfer = this.#activeTransfers.get(msg.uuid);
231
- if (isNotUndefined(transfer)) {
232
- transfer.expectedSize = msg.size;
233
- transfer.metadata = msg.metadata;
234
- }
235
- else {
236
- console.warn(`[P2P] Received sample start for unknown transfer: ${msg.uuid}`);
237
- }
238
- }
239
- else if (msg.type === "complete") {
240
- console.log(`[P2P] Sample transfer complete from ${peerId}: ${msg.uuid}`);
241
- this.#complete(msg.uuid);
242
- }
243
- else if (msg.type === "request") {
244
- console.log(`[P2P] Sample request from ${peerId}: ${msg.uuid}`);
245
- this.#handleSampleRequest(msg.uuid);
246
- }
247
- }
248
- else {
249
- const uuid = Array.from(this.#activeTransfers.keys()).find(id => {
250
- const transfer = this.#activeTransfers.get(id);
251
- if (isNotUndefined(transfer)) {
252
- return transfer.receivedSize < transfer.expectedSize;
253
- }
254
- return false;
255
- });
256
- if (isNotUndefined(uuid)) {
257
- const transfer = this.#activeTransfers.get(uuid);
258
- if (isNotUndefined(transfer)) {
259
- transfer.chunks.push(data);
260
- transfer.receivedSize += data.byteLength;
261
- const progress = transfer.receivedSize / transfer.expectedSize;
262
- console.log(`[P2P] Receiving sample ${uuid}: ${Math.round(progress * 100)}% (${transfer.receivedSize}/${transfer.expectedSize} bytes)`);
263
- transfer.progress(progress);
264
- }
265
- }
266
- }
267
- }
268
- #handleSampleRequest(uuid) {
269
- SampleStorage.loadSample(UUID.parse(uuid)).then(async ([audio, peaks, meta]) => {
270
- console.log(`[P2P] Responding to sample request: ${uuid}`);
271
- const zipData = await this.#createZipFile(audio, peaks.toArrayBuffer(), meta);
272
- this.#peerChannels.forEach(channel => {
273
- if (channel.readyState === "open") {
274
- this.#sendSample(channel, uuid, zipData, meta);
275
- }
276
- });
277
- }).catch(error => {
278
- console.log(`[P2P] Sample not available for request: ${uuid}`, error);
279
- });
280
- }
281
- async #complete(uuid) {
282
- const transfer = asDefined(this.#activeTransfers.get(uuid), `Expected active transfer for ${uuid}`);
283
- console.log(`[P2P] Reconstructing sample: ${uuid}`);
284
- const zipBuffer = new ArrayBuffer(transfer.expectedSize);
285
- const zipView = new Uint8Array(zipBuffer);
286
- let offset = 0;
287
- transfer.chunks.forEach(chunk => {
288
- zipView.set(new Uint8Array(chunk), offset);
289
- offset += chunk.byteLength;
290
- });
291
- try {
292
- const { audioData, peaks, metadata } = await this.#extractZipFile(zipBuffer);
293
- await SampleStorage.saveSample({
294
- uuid: UUID.parse(uuid),
295
- audio: audioData,
296
- peaks: peaks,
297
- meta: metadata
298
- });
299
- console.log(`[P2P] Sample saved successfully: ${uuid}`);
300
- transfer.resolve([audioData, metadata]);
301
- this.#pendingFetches.delete(uuid);
302
- }
303
- catch (error) {
304
- console.error(`[P2P] Failed to save sample ${uuid}:`, error);
305
- transfer.reject(error);
306
- this.#pendingFetches.delete(uuid);
307
- }
308
- finally {
309
- clearTimeout(transfer.timeout);
310
- }
311
- this.#activeTransfers.delete(uuid);
312
- }
313
- #getMyId() {
314
- return this.#provider.awareness.clientID.toString();
315
- }
316
- #audioDataToArrayBuffer(audioData) {
317
- const totalSamples = audioData.numberOfFrames * audioData.numberOfChannels;
318
- const buffer = new ArrayBuffer(4 + 4 + 4 + totalSamples * 4);
319
- const view = new DataView(buffer);
320
- view.setUint32(0, audioData.sampleRate, true);
321
- view.setUint32(4, audioData.numberOfFrames, true);
322
- view.setUint32(8, audioData.numberOfChannels, true);
323
- let offset = 12;
324
- for (let frame = 0; frame < audioData.numberOfFrames; frame++) {
325
- for (let channel = 0; channel < audioData.numberOfChannels; channel++) {
326
- view.setFloat32(offset, audioData.frames[channel][frame], true);
327
- offset += 4;
328
- }
329
- }
330
- return buffer;
331
- }
332
- #arrayBufferToAudioData(buffer) {
333
- const view = new DataView(buffer);
334
- const sampleRate = view.getUint32(0, true);
335
- const numberOfFrames = view.getUint32(4, true);
336
- const numberOfChannels = view.getUint32(8, true);
337
- const frames = [];
338
- for (let channel = 0; channel < numberOfChannels; channel++) {
339
- frames.push(new Float32Array(numberOfFrames));
340
- }
341
- let offset = 12;
342
- for (let frame = 0; frame < numberOfFrames; frame++) {
343
- for (let channel = 0; channel < numberOfChannels; channel++) {
344
- frames[channel][frame] = view.getFloat32(offset, true);
345
- offset += 4;
346
- }
347
- }
348
- return { sampleRate, numberOfFrames, numberOfChannels, frames };
349
- }
350
- }