diff --git a/package-lock.json b/package-lock.json index c2723e6e..3b1daeb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8329,9 +8329,9 @@ "license": "MIT" }, "node_modules/mediabunny": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.39.0.tgz", - "integrity": "sha512-NDeht2Sf7kYfrUu7b/9AhVK4KdQhy6UeTF/EPNXW+PdpIYvSv36ZQ/SRBoFAJN2LRl6XrGb8JYY4MCnhsGQ2yg==", + "version": "1.39.1", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.39.1.tgz", + "integrity": "sha512-gaQKDWtfLhgSN9tRj37lgznHf9MOvey7sbT6GdQA7mWl1kaE5R+P1lfl9CDsgtzongx12IG9X/y2vgZmTHzGDg==", "license": "MPL-2.0", "workspaces": [ "packages/*" diff --git a/packages/studio/core/src/AssetService.ts b/packages/studio/core/src/AssetService.ts index 1902f7c5..8eba7850 100644 --- a/packages/studio/core/src/AssetService.ts +++ b/packages/studio/core/src/AssetService.ts @@ -22,6 +22,7 @@ export namespace AssetService { export type ImportArgs = { uuid?: UUID.Bytes name?: string, + bpm?: number, arrayBuffer: ArrayBuffer, progressHandler?: Progress.Handler, origin?: "import" | "recording" diff --git a/packages/studio/core/src/RecordingWorklet.ts b/packages/studio/core/src/RecordingWorklet.ts index 239942d6..0ebc69f2 100644 --- a/packages/studio/core/src/RecordingWorklet.ts +++ b/packages/studio/core/src/RecordingWorklet.ts @@ -1,4 +1,15 @@ -import {int, Notifier, Observer, Option, panic, Procedure, Subscription, Terminable, Terminator, UUID} from "@opendaw/lib-std" +import { + int, + Notifier, + Observer, + Option, + panic, + Procedure, + Subscription, + Terminable, + Terminator, + UUID +} from "@opendaw/lib-std" import {AudioData} from "@opendaw/lib-dsp" import {Peaks} from "@opendaw/lib-fusion" import {mergeChunkPlanes, RingBuffer, SampleLoader, SampleLoaderState} from "@opendaw/studio-adapters" @@ -23,6 +34,7 @@ export class RecordingWorklet extends AudioWorkletNode implements Terminable, Sa #state: SampleLoaderState = {type: "record"} #onSaved: Option> = Option.None #sampleService: Option = Option.None + #bpm: Option = Option.None constructor(context: BaseAudioContext, config: RingBuffer.Config) { super(context, "recording-processor", { @@ -57,6 +69,7 @@ export class RecordingWorklet extends AudioWorkletNode implements Terminable, Sa } set onSaved(callback: Procedure) {this.#onSaved = Option.wrap(callback)} + set bpm(value: number) {this.#bpm = Option.wrap(value)} set sampleService(service: SampleService) {this.#sampleService = Option.wrap(service)} setFillLength(value: int): void {this.#peakWriter.numFrames = value} @@ -94,7 +107,9 @@ export class RecordingWorklet extends AudioWorkletNode implements Terminable, Sa const audioData = AudioData.create(this.context.sampleRate, totalSamples, this.channelCount) mergedFrames.forEach((frame, index) => audioData.frames[index].set(frame)) this.#data = Option.wrap(audioData) - const sample = await this.#sampleService.unwrap("SampleService not set").importRecording(audioData) + const sample = await this.#sampleService + .unwrap("SampleService not set") + .importRecording(audioData, this.#bpm.unwrapOrElse(120)) this.#onSaved.ifSome(callback => callback(UUID.parse(sample.uuid))) this.#setState({type: "loaded"}) this.terminate() diff --git a/packages/studio/core/src/capture/CaptureAudio.ts b/packages/studio/core/src/capture/CaptureAudio.ts index d820d25f..b12efdb5 100644 --- a/packages/studio/core/src/capture/CaptureAudio.ts +++ b/packages/studio/core/src/capture/CaptureAudio.ts @@ -122,6 +122,7 @@ export class CaptureAudio extends Capture { } const {gainNode, channelCount} = audioChain const recordingWorklet = audioWorklets.createRecording(channelCount, RenderQuantum) + recordingWorklet.bpm = project.timelineBox.bpm.getValue() recordingWorklet.sampleService = sampleService sampleManager.record(recordingWorklet) gainNode.connect(recordingWorklet) diff --git a/packages/studio/core/src/capture/RecordAudio.ts b/packages/studio/core/src/capture/RecordAudio.ts index 8198f268..76cd50a3 100644 --- a/packages/studio/core/src/capture/RecordAudio.ts +++ b/packages/studio/core/src/capture/RecordAudio.ts @@ -124,7 +124,27 @@ export namespace RecordAudio { currentTake = Option.wrap(createTakeRegion(position, currentWaveformOffset, previousTrack)) } - recordingWorklet.onSaved = uuid => project.trackUserCreatedSample(uuid) + recordingWorklet.onSaved = uuid => { + project.trackUserCreatedSample(uuid) + editing.modify(() => { + fileBox.ifSome(oldFileBox => { + editing.modify(() => { + const newFileBox = AudioFileBox.create(boxGraph, uuid, box => { + box.fileName.setValue(oldFileBox.fileName.getValue()) + box.startInSeconds.setValue(oldFileBox.startInSeconds.getValue()) + box.endInSeconds.setValue(oldFileBox.endInSeconds.getValue()) + }) + for (const pointer of [...oldFileBox.pointerHub.incoming()]) { + pointer.refer(newFileBox) + } + for (const pointer of [...oldFileBox.transientMarkers.pointerHub.incoming()]) { + pointer.refer(newFileBox.transientMarkers) + } + oldFileBox.delete() + }) + }) + }) + } terminator.ownAll( Terminable.create(() => { tryCatch(() => sourceNode.disconnect(recordingWorklet)) diff --git a/packages/studio/core/src/samples/SampleService.ts b/packages/studio/core/src/samples/SampleService.ts index a475d900..2f36512e 100644 --- a/packages/studio/core/src/samples/SampleService.ts +++ b/packages/studio/core/src/samples/SampleService.ts @@ -1,4 +1,4 @@ -import {Arrays, Class, Progress, tryCatch, UUID} from "@opendaw/lib-std" +import {Arrays, Class, isDefined, Progress, tryCatch, UUID} from "@opendaw/lib-std" import {Box} from "@opendaw/lib-box" import {AudioData, estimateBpm} from "@opendaw/lib-dsp" import {Promises} from "@opendaw/lib-runtime" @@ -20,18 +20,19 @@ export class SampleService extends AssetService { constructor(readonly audioContext: AudioContext) {super()} - async importRecording(audioData: AudioData, name: string = "Recording"): Promise { + async importRecording(audioData: AudioData, bpm: number, name: string = "Recording"): Promise { const arrayBuffer = WavFile.encodeFloats({ frames: audioData.frames.slice(), numberOfFrames: audioData.numberOfFrames, numberOfChannels: audioData.numberOfChannels, sampleRate: audioData.sampleRate }) - return this.importFile({name, arrayBuffer, origin: "recording"}) + return this.importFile({name, bpm, arrayBuffer, origin: "recording"}) } - async importFile({uuid, name, arrayBuffer, progressHandler = Progress.Empty, origin = "import"} - : AssetService.ImportArgs): Promise { + async importFile({uuid, name, bpm, arrayBuffer, progressHandler = Progress.Empty, origin = "import"} + : AssetService.ImportArgs, + transformMeta?: (meta: SampleMetaData, audioData: Readonly) => Promise): Promise { console.debug(`importSample '${name}' (${arrayBuffer.byteLength >> 10}kb)`) uuid ??= await UUID.sha256(arrayBuffer) const audioData = await this.#decodeAudio(arrayBuffer) @@ -44,14 +45,17 @@ export class SampleService extends AssetService { audioData.numberOfFrames, audioData.numberOfChannels) as ArrayBuffer const meta: SampleMetaData = { - bpm: estimateBpm(duration), + bpm: bpm ?? estimateBpm(duration), name: name ?? "Unnnamed", duration, sample_rate: audioData.sampleRate, origin } - await SampleStorage.get().save({uuid, audio: audioData, peaks, meta}) + if (isDefined(transformMeta)) { + await transformMeta(meta, audioData) + } const sample = {uuid: UUID.toString(uuid), ...meta} + await SampleStorage.get().save({uuid, audio: audioData, peaks, meta}) this.notifier.notify(sample) return sample }