99
1010import { getFile } from './hub.js' ;
1111import { FFT , max } from './maths.js' ;
12- import { calculateReflectOffset , saveBlob } from './core.js' ;
12+ import { calculateReflectOffset } from './core.js' ;
13+ import { saveBlob } from './io.js' ;
1314import { apis } from '../env.js' ;
1415import { Tensor , matmul } from './tensor.js' ;
15- import fs from 'node:fs' ;
1616
1717/**
1818 * Helper function to read audio from a path/URL.
@@ -733,23 +733,24 @@ export function window_function(window_length, name, { periodic = true, frame_le
733733}
734734
735735/**
736- * Encode audio data to a WAV file.
736+ * Efficiently encode audio data to a WAV file.
737737 * WAV file specs : https://en.wikipedia.org/wiki/WAV#WAV_File_header
738738 *
739739 * Adapted from https://www.npmjs.com/package/audiobuffer-to-wav
740- * @param {Float32Array } samples The audio samples.
740+ * @param {Float32Array[] } chunks The audio samples.
741741 * @param {number } rate The sample rate.
742- * @returns {ArrayBuffer } The WAV audio buffer .
742+ * @returns {Blob } The WAV file as a Blob .
743743 */
744- function encodeWAV ( samples , rate ) {
745- let offset = 44 ;
746- const buffer = new ArrayBuffer ( offset + samples . length * 4 ) ;
744+ function encodeWAV ( chunks , rate ) {
745+ const totalLength = chunks . reduce ( ( acc , chunk ) => acc + chunk . length , 0 ) ;
746+
747+ const buffer = new ArrayBuffer ( 44 ) ;
747748 const view = new DataView ( buffer ) ;
748749
749750 /* RIFF identifier */
750751 writeString ( view , 0 , 'RIFF' ) ;
751752 /* RIFF chunk length */
752- view . setUint32 ( 4 , 36 + samples . length * 4 , true ) ;
753+ view . setUint32 ( 4 , 36 + totalLength * 4 , true ) ;
753754 /* RIFF type */
754755 writeString ( view , 8 , 'WAVE' ) ;
755756 /* format chunk identifier */
@@ -771,13 +772,10 @@ function encodeWAV(samples, rate) {
771772 /* data chunk identifier */
772773 writeString ( view , 36 , 'data' ) ;
773774 /* data chunk length */
774- view . setUint32 ( 40 , samples . length * 4 , true ) ;
775-
776- for ( let i = 0 ; i < samples . length ; ++ i , offset += 4 ) {
777- view . setFloat32 ( offset , samples [ i ] , true ) ;
778- }
775+ view . setUint32 ( 40 , totalLength * 4 , true ) ;
779776
780- return buffer ;
777+ // @ts -expect-error TS2322
778+ return new Blob ( [ buffer , ...chunks ] , { type : "audio/wav" } ) ;
781779}
782780
783781function writeString ( view , offset , string ) {
@@ -789,7 +787,7 @@ function writeString(view, offset, string) {
789787export class RawAudio {
790788 /**
791789 * Create a new `RawAudio` object.
792- * @param {Float32Array } audio Audio data
790+ * @param {Float32Array|Float32Array[] } audio Audio data, either as a single `Float32Array` chunk or multiple `Float32Array` chunks.
793791 * @param {number } sampling_rate Sampling rate of the audio data
794792 */
795793 constructor ( audio , sampling_rate ) {
@@ -798,44 +796,49 @@ export class RawAudio {
798796 }
799797
800798 /**
801- * Convert the audio to a wav file buffer .
802- * @returns {ArrayBuffer } The WAV file .
799+ * Get the audio data, accumulating all chunks if necessary .
800+ * @returns {Float32Array } The audio data .
803801 */
804- toWav ( ) {
805- return encodeWAV ( this . audio , this . sampling_rate ) ;
802+ get data ( ) {
803+ if ( Array . isArray ( this . audio ) ) {
804+ if ( this . audio . length === 0 ) {
805+ return new Float32Array ( 0 ) ;
806+ }
807+ if ( this . audio . length === 1 ) {
808+ return this . audio [ 0 ] ;
809+ }
810+ // Concatenate all chunks into a single Float32Array
811+ const totalLength = this . audio . reduce ( ( acc , chunk ) => acc + chunk . length , 0 ) ;
812+ const result = new Float32Array ( totalLength ) ;
813+ let offset = 0 ;
814+ for ( const chunk of this . audio ) {
815+ result . set ( chunk , offset ) ;
816+ offset += chunk . length ;
817+ }
818+ return result ;
819+ } else {
820+ return this . audio ;
821+ }
806822 }
807823
808824 /**
809825 * Convert the audio to a blob.
810826 * @returns {Blob }
811827 */
812828 toBlob ( ) {
813- const wav = this . toWav ( ) ;
814- const blob = new Blob ( [ wav ] , { type : 'audio/wav' } ) ;
815- return blob ;
829+ let audio = this . audio ;
830+ if ( audio instanceof Float32Array ) {
831+ audio = [ audio ] ; // Ensure audio is an array of chunks
832+ }
833+ return encodeWAV ( audio , this . sampling_rate ) ;
816834 }
817835
818836 /**
819837 * Save the audio to a wav file.
820838 * @param {string } path
839+ * @returns {Promise<void> }
821840 */
822841 async save ( path ) {
823- let fn ;
824-
825- if ( apis . IS_BROWSER_ENV ) {
826- if ( apis . IS_WEBWORKER_ENV ) {
827- throw new Error ( 'Unable to save a file from a Web Worker.' ) ;
828- }
829- fn = saveBlob ;
830- } else if ( apis . IS_FS_AVAILABLE ) {
831- fn = async ( /** @type {string } */ path , /** @type {Blob } */ blob ) => {
832- let buffer = await blob . arrayBuffer ( ) ;
833- fs . writeFileSync ( path , Buffer . from ( buffer ) ) ;
834- } ;
835- } else {
836- throw new Error ( 'Unable to save because filesystem is disabled in this environment.' ) ;
837- }
838-
839- await fn ( path , this . toBlob ( ) ) ;
842+ return saveBlob ( path , this . toBlob ( ) ) ;
840843 }
841844}
0 commit comments