From 8be8c162e9fb5cc2e43d78452ba126960c2c1d41 Mon Sep 17 00:00:00 2001 From: RMO Date: Thu, 28 Aug 2025 03:27:56 -0600 Subject: [PATCH] docs: annotate sample modules --- packages/app/studio/src/service/SampleApi.ts | 5 ++++ .../app/studio/src/service/SamplePlayback.ts | 12 ++++++++- .../app/studio/src/service/SampleService.ts | 2 ++ .../studio/src/ui/browse/SampleBrowser.tsx | 1 + .../studio/src/ui/browse/SampleDialogs.tsx | 4 +-- .../studio/src/ui/browse/SampleLocation.tsx | 2 ++ .../docs-dev/architecture/sample-storage.md | 2 ++ .../docs-user/features/file-management.md | 1 + .../src/samples/MainThreadSampleLoader.ts | 6 +++++ .../src/samples/MainThreadSampleManager.ts | 13 ++++++++++ .../studio/core/src/samples/SampleProvider.ts | 1 + .../studio/core/src/samples/SampleStorage.ts | 25 +++++++++++++++++-- 12 files changed, 69 insertions(+), 5 deletions(-) diff --git a/packages/app/studio/src/service/SampleApi.ts b/packages/app/studio/src/service/SampleApi.ts index 44f7b78c8..279382183 100644 --- a/packages/app/studio/src/service/SampleApi.ts +++ b/packages/app/studio/src/service/SampleApi.ts @@ -49,6 +49,8 @@ export namespace SampleApi { /** * Fetch metadata for all available samples. + * + * @returns array of sample metadata entries */ export const all = async (): Promise> => { return await Promises.retry(() => @@ -63,6 +65,7 @@ export namespace SampleApi { * Retrieve metadata for a single sample. * * @param uuid identifier of the sample to fetch. + * @returns metadata for the requested sample */ export const get = async (uuid: UUID.Format): Promise => { const url = `${ApiRoot}/get.php?uuid=${UUID.toString(uuid)}`; @@ -86,6 +89,7 @@ export namespace SampleApi { * @param context audio context used for decoding. * @param uuid sample identifier. * @param progress callback receiving loading progress between 0 and 1. + * @returns decoded audio data and metadata */ export const load = async ( context: AudioContext, @@ -150,6 +154,7 @@ export namespace SampleApi { * * @param arrayBuffer raw WAV data. * @param metaData description of the sample to accompany the upload. + * @returns void */ export const upload = async ( arrayBuffer: ArrayBuffer, diff --git a/packages/app/studio/src/service/SamplePlayback.ts b/packages/app/studio/src/service/SamplePlayback.ts index 64ce37fab..b2bfbc82b 100644 --- a/packages/app/studio/src/service/SamplePlayback.ts +++ b/packages/app/studio/src/service/SamplePlayback.ts @@ -63,6 +63,9 @@ export class SamplePlayback { /** * Toggle playback of a given sample. If it is currently playing it will * stop, otherwise it will buffer and start playback. + * + * @param uuidAsString identifier of the sample to play + * @returns void */ toggle(uuidAsString: string): void { if (this.#current.contains(uuidAsString)) { @@ -104,7 +107,10 @@ export class SamplePlayback { } } - /** Stop playback and reset the internal state. */ + /** Stop playback and reset the internal state. + * + * @returns void + */ eject(): void { this.#current.ifSome((uuid) => this.#notify(uuid, { type: "idle" })); this.#current = Option.None; @@ -114,6 +120,10 @@ export class SamplePlayback { /** * Subscribe to playback events for a particular sample. + * + * @param uuidAsString identifier of the sample to observe + * @param procedure callback invoked with playback events + * @returns subscription that can be terminated to stop updates */ subscribe( uuidAsString: string, diff --git a/packages/app/studio/src/service/SampleService.ts b/packages/app/studio/src/service/SampleService.ts index 709e3d337..496b9a8be 100644 --- a/packages/app/studio/src/service/SampleService.ts +++ b/packages/app/studio/src/service/SampleService.ts @@ -3,6 +3,8 @@ * * Re-exports {@link SampleService} so consumers can import from the service * layer without referencing UI internals. + * + * @packageDocumentation */ export { SampleService } from "../ui/browse/SampleService"; diff --git a/packages/app/studio/src/ui/browse/SampleBrowser.tsx b/packages/app/studio/src/ui/browse/SampleBrowser.tsx index 1f135666c..76c8cc32c 100644 --- a/packages/app/studio/src/ui/browse/SampleBrowser.tsx +++ b/packages/app/studio/src/ui/browse/SampleBrowser.tsx @@ -60,6 +60,7 @@ const location = new DefaultObservableValue(SampleLocation.Cloud); * * @param lifecycle lifecycle controlling subscriptions * @param service access to studio level services + * @returns rendered element for inclusion in the DOM */ export const SampleBrowser = ({ lifecycle, service }: Construct) => { lifecycle.own({ terminate: () => service.samplePlayback.eject() }); diff --git a/packages/app/studio/src/ui/browse/SampleDialogs.tsx b/packages/app/studio/src/ui/browse/SampleDialogs.tsx index fa0526b22..6c5d6733d 100644 --- a/packages/app/studio/src/ui/browse/SampleDialogs.tsx +++ b/packages/app/studio/src/ui/browse/SampleDialogs.tsx @@ -25,8 +25,8 @@ export namespace SampleDialogs { /** * Open the browser's file picker for selecting sample files. * - * @param multiple Allow selection of multiple files. - * @returns Result of the picker invocation. + * @param multiple allow selection of multiple files + * @returns promise resolving with the picker result */ export const nativeFileBrowser = async (multiple: boolean = true) => Promises.tryCatch( diff --git a/packages/app/studio/src/ui/browse/SampleLocation.tsx b/packages/app/studio/src/ui/browse/SampleLocation.tsx index 304c565f5..b86cbe5d8 100644 --- a/packages/app/studio/src/ui/browse/SampleLocation.tsx +++ b/packages/app/studio/src/ui/browse/SampleLocation.tsx @@ -3,6 +3,8 @@ * * Used by {@link SampleBrowser} and {@link SampleService} to toggle between * cloud and OPFS backed libraries. + * + * @enum */ export const enum SampleLocation { /** Sample hosted on the server */ diff --git a/packages/docs/docs-dev/architecture/sample-storage.md b/packages/docs/docs-dev/architecture/sample-storage.md index c501431ac..d65b76910 100644 --- a/packages/docs/docs-dev/architecture/sample-storage.md +++ b/packages/docs/docs-dev/architecture/sample-storage.md @@ -25,3 +25,5 @@ The [`SampleStorage` namespace](../../../packages/studio/core/src/samples/Sample 4. `SampleStorage.list` enumerates stored samples for the **Sample Browser**. Clients may call `SampleStorage.remove` to free space or `SampleStorage.updateMeta` to adjust metadata without touching the audio. + +The `MainThreadSampleManager` uses a `SampleProvider` (such as `SampleApi`) to fetch audio when it cannot be found locally. Once retrieved, `MainThreadSampleLoader` persists the data back to OPFS via `SampleStorage` so subsequent requests are served from disk rather than the network. diff --git a/packages/docs/docs-user/features/file-management.md b/packages/docs/docs-user/features/file-management.md index 230de4121..03ea7ff10 100644 --- a/packages/docs/docs-user/features/file-management.md +++ b/packages/docs/docs-user/features/file-management.md @@ -12,6 +12,7 @@ offline. Developers can dive deeper in the - Search, preview and delete samples directly from the list. - Adjust preview volume with the slider in the browser footer. - Local samples are cached in OPFS and survive page reloads. + Once downloaded they can be reused offline until removed. ### Manage Local Storage diff --git a/packages/studio/core/src/samples/MainThreadSampleLoader.ts b/packages/studio/core/src/samples/MainThreadSampleLoader.ts index 1ac7be163..adb626e32 100644 --- a/packages/studio/core/src/samples/MainThreadSampleLoader.ts +++ b/packages/studio/core/src/samples/MainThreadSampleLoader.ts @@ -53,6 +53,8 @@ export class MainThreadSampleLoader implements SampleLoader { /** * Drop any cached data and restart the loading process. + * + * @returns void */ invalidate(): void { this.#state = {type: "progress", progress: 0.0} @@ -67,6 +69,7 @@ export class MainThreadSampleLoader implements SampleLoader { * Subscribe to state changes. * * @param observer callback receiving loader state updates + * @returns subscription handle that can be terminated */ subscribe(observer: Observer): Subscription { if (this.#state.type === "loaded") { @@ -92,6 +95,9 @@ export class MainThreadSampleLoader implements SampleLoader { * * If loading has not yet completed the promise resolves once data becomes * available. + * + * @param zip archive instance to receive the files + * @returns resolves once the files have been added */ async pipeFilesInto(zip: JSZip): Promise { const exec: Exec = async () => { diff --git a/packages/studio/core/src/samples/MainThreadSampleManager.ts b/packages/studio/core/src/samples/MainThreadSampleManager.ts index 86fe9aba6..033299cf2 100644 --- a/packages/studio/core/src/samples/MainThreadSampleManager.ts +++ b/packages/studio/core/src/samples/MainThreadSampleManager.ts @@ -30,6 +30,10 @@ export class MainThreadSampleManager implements SampleManager, SampleProvider { /** * Fetch sample data from the backing provider. + * + * @param uuid identifier of the requested sample + * @param progress receives loading progress between 0 and 1 + * @returns audio data and metadata for the sample */ fetch(uuid: UUID.Format, progress: Progress.Handler): Promise<[AudioData, SampleMetaData]> { return this.#api.fetch(uuid, progress) @@ -37,16 +41,25 @@ export class MainThreadSampleManager implements SampleManager, SampleProvider { /** * Invalidate the loader for a given sample. + * + * @param uuid sample to invalidate + * @returns void */ invalidate(uuid: UUID.Format) {this.#loaders.opt(uuid).ifSome(loader => loader.invalidate())} /** * Register a loader with the manager so it can be invalidated later. + * + * @param loader loader instance to track + * @returns void */ record(loader: SampleLoader): void {this.#loaders.add(loader)} /** * Retrieve an existing loader or create a new one. + * + * @param uuid identifier of the desired sample + * @returns corresponding loader instance */ getOrCreate(uuid: UUID.Format): SampleLoader { return this.#loaders.getOrCreate(uuid, uuid => new MainThreadSampleLoader(this, uuid)) diff --git a/packages/studio/core/src/samples/SampleProvider.ts b/packages/studio/core/src/samples/SampleProvider.ts index d417ff10f..0be325280 100644 --- a/packages/studio/core/src/samples/SampleProvider.ts +++ b/packages/studio/core/src/samples/SampleProvider.ts @@ -10,6 +10,7 @@ export interface SampleProvider { * * @param uuid identifier of the requested sample * @param progress callback receiving download progress between 0 and 1 + * @returns audio data and metadata for the sample */ fetch(uuid: UUID.Format, progress: Progress.Handler): Promise<[AudioData, SampleMetaData]> } diff --git a/packages/studio/core/src/samples/SampleStorage.ts b/packages/studio/core/src/samples/SampleStorage.ts index b371cf053..354e28190 100644 --- a/packages/studio/core/src/samples/SampleStorage.ts +++ b/packages/studio/core/src/samples/SampleStorage.ts @@ -16,6 +16,12 @@ export namespace SampleStorage { /** * Write decoded audio, peaks and metadata to OPFS. + * + * @param uuid identifier of the sample to store + * @param audio decoded audio frames + * @param peaks precomputed peak data + * @param meta additional sample metadata + * @returns resolves when the files have been written */ export const store = async (uuid: UUID.Format, audio: AudioData, @@ -35,6 +41,10 @@ export namespace SampleStorage { /** * Overwrite only the metadata file of a stored sample. + * + * @param uuid identifier of the sample to update + * @param meta new metadata to persist + * @returns resolves once the metadata file was written */ export const updateMeta = async (uuid: UUID.Format, meta: SampleMetaData): Promise => { const path = `${Folder}/${UUID.toString(uuid)}` @@ -43,6 +53,10 @@ export namespace SampleStorage { /** * Load a sample from OPFS and decode it into {@link AudioData} and peaks. + * + * @param uuid identifier of the sample to load + * @param context audio context used for decoding + * @returns audio data, peak information and metadata */ export const load = async (uuid: UUID.Format, context: AudioContext): Promise<[AudioData, Peaks, SampleMetaData]> => { const path = `${Folder}/${UUID.toString(uuid)}` @@ -61,13 +75,20 @@ export namespace SampleStorage { }, peaks, meta]) } - /** Delete a sample and all related files. */ + /** Delete a sample and all related files. + * + * @param uuid identifier of the sample to delete + * @returns resolves when the files have been removed + */ export const remove = async (uuid: UUID.Format): Promise => { const path = `${Folder}/${UUID.toString(uuid)}` return WorkerAgents.Opfs.delete(`${path}`) } - /** List metadata for all stored samples. */ + /** List metadata for all stored samples. + * + * @returns metadata for each sample stored in OPFS + */ export const list = async (): Promise> => { return WorkerAgents.Opfs.list(Folder) .then(files => Promise.all(files.filter(file => file.kind === "directory")