From f6e58d1d0ab6279f13b86ad9bd67db7a3281099c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20P=C3=B6mp?= Date: Tue, 19 Nov 2024 16:41:52 +0100 Subject: [PATCH] fix(web-media): splitting audio files not working --- libs/web-media/src/lib/audio/audio-cutter.ts | 169 +++++++++++++------ 1 file changed, 115 insertions(+), 54 deletions(-) diff --git a/libs/web-media/src/lib/audio/audio-cutter.ts b/libs/web-media/src/lib/audio/audio-cutter.ts index 4f6cd77f4..9bc21f474 100644 --- a/libs/web-media/src/lib/audio/audio-cutter.ts +++ b/libs/web-media/src/lib/audio/audio-cutter.ts @@ -1,8 +1,9 @@ import { NumeratedSegment } from '@octra/media'; import { Subject } from 'rxjs'; -import { WavWriter } from './binary'; +import { FileInfo } from '../data-info'; import { IntArray } from './AudioFormats'; import { AudioInfo } from './audio-info'; +import { WavWriter } from './binary'; export class AudioCutter { private status: 'running' | 'stopRequested' | 'stopped' = 'stopped'; @@ -129,7 +130,11 @@ export class AudioCutter { case 'sampleStart': return segment.sampleStart; case 'sampleDur': - return segment.sampleDur ?? (this.audioInfo.audioBufferInfo?.samples ?? this.audioInfo.duration.samples) - segment.sampleStart; + return ( + segment.sampleDur ?? + (this.audioInfo.audioBufferInfo?.samples ?? + this.audioInfo.duration.samples) - segment.sampleStart + ); case 'secondsStart': return ( Math.round( @@ -139,7 +144,11 @@ export class AudioCutter { case 'secondsDur': return ( Math.round( - (((this.audioInfo.audioBufferInfo?.samples ?? this.audioInfo.duration.samples) - segment.sampleStart) / this.audioInfo.sampleRate) * 1000 + (((this.audioInfo.audioBufferInfo?.samples ?? + this.audioInfo.duration.samples) - + segment.sampleStart) / + this.audioInfo.sampleRate) * + 1000 ) / 1000 ); } @@ -223,51 +232,107 @@ export class AudioCutter { public splitChannelsToFiles( filename: string, - type: string, - buffer: ArrayBuffer + selectedChannels: number[], + buffer: ArrayBuffer, + channelData?: Float32Array[] ): Promise { return new Promise((resolve, reject) => { const result: File[] = []; + const promises2: Promise[] = []; + const sampleRate = + this.audioInfo.audioBufferInfo?.sampleRate ?? this.audioInfo.sampleRate; + const { name, extension } = FileInfo.extractFileName(filename); if (this.audioInfo.channels > 1) { - const wavWriter = new WavWriter(); - const u8array = new Uint8Array(buffer); - - const promises: Promise[] = []; - promises.push( - this.extractDataFromArray( - 0, - this.audioInfo.duration.samples, - u8array, - 0 - ) - ); - promises.push( - this.extractDataFromArray( - 0, - this.audioInfo.duration.samples, - u8array, - 1 - ) - ); + if (extension === '.wav') { + // cut wave file + const promises: Promise[] = []; + const u8array = new Uint8Array(buffer); + + for (const selectedChannel of selectedChannels) { + promises.push( + this.extractDataFromArray( + 0, + this.audioInfo.duration.samples, + u8array, + selectedChannel + ) + ); + } + Promise.all(promises) + .then((extracts) => { + resolve( + selectedChannels.map((a, i) => + this.getFileFromBufferPart( + extracts[i], + 1, + `${name}_${a + 1}.wav` + ) + ) + ); + }) + .catch((error) => { + console.error(error); + reject(error); + }); + } else { + // not wave + if (!channelData) { + // decode audio and return each channelData + const context = new AudioContext(); + context + .decodeAudioData(buffer) + .then((audioBuffer) => { + channelData = selectedChannels.map((i) => + audioBuffer.getChannelData(i) + ); + const wavWriter = new WavWriter(); + return selectedChannels.map((i) => + wavWriter.writeAsync([channelData![i]], sampleRate) + ); + }) + .then((promises) => { + Promise.all(promises) + .then((files) => { + resolve( + selectedChannels.map( + (a, i) => + new File([files[i]], `${name}_${a + 1}.wav`, { + type: 'audio/wav', + }) + ) + ); + }) + .catch((error) => { + reject(error); + }); + }); + } else { + const wavWriter = new WavWriter(); + promises2.push( + ...selectedChannels.map(( i) => + wavWriter.writeAsync([channelData![i]], sampleRate) + ) + ); + } + } - Promise.all(promises) - .then((extracts) => { - for (let i = 0; i < extracts.length; i++) { - const extract = extracts[i]; - result.push( - this.getFileFromBufferPart( - extract as any, - 1, - `${filename}_${i + 1}` + if (promises2.length > 0) { + Promise.all(promises2) + .then((files) => { + resolve( + selectedChannels.map( + (a, i) => + new File([files[i]], `${name}_${a + 1}.wav`, { + type: 'audio/wav', + }) ) ); - } - resolve(result); - }) - .catch((error) => { - reject(error); - }); + }) + .catch((error) => { + reject(error); + }); + } } else { reject(`can't split audio file because it contains one channel only.`); } @@ -276,20 +341,6 @@ export class AudioCutter { }); } - private getFileFromBufferPart( - data: IntArray, - channels: number, - filename: string - ): File { - return new File( - [this.getFileDataView(data as any, channels)], - `${filename}.wav`, - { - type: 'audio/wav', - } - ); - } - /*** * cuts the data part of selected samples from an Uint8Array * @param sampleStart the start of the extraction @@ -379,4 +430,14 @@ export class AudioCutter { this.status = 'stopRequested'; } } + + private getFileFromBufferPart( + data: IntArray, + channels: number, + filename: string + ): File { + return new File([this.getFileDataView(data as any, channels)], filename, { + type: 'audio/wav', + }); + } }