stem_separator / converter_worker.js
joserpagil's picture
Upload 4 files
5ecc86a verified
Raw
History Blame Contribute Delete
6.15 kB
importScripts( "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" )
importScripts( "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgpu/dist/tf-backend-webgpu.js" )
importScripts( "https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js" )
//importScripts( "https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.webgpu.min.js" )
const create_hann = size =>{
return tf.tidy(() => {
const indices = tf.range(0, size, 1);
return tf.cos(indices.mul(2 * Math.PI / (size - 1))).mul(0.5).sub(0.5).mul(-2);
/*
const window = tf.cos(indices.mul(2 * Math.PI / (size - 1))).mul(0.5).sub(0.5).mul(-2);
const normalizationFactor = window.sum();
return window.div(normalizationFactor);
*/
});
}
const stft =( signal, frameSize, hopSize, windowFunc = 'hann')=>{
return tf.tidy(() => {
// Get signal length and calculate number of frames
const signalLength = signal.shape[0];
const numFrames = Math.floor((signalLength - frameSize) / hopSize) + 1;
// Generate window
const window = create_hann( frameSize )
// Prepare output array
const frames = [];
// Process each frame
for (let i = 0; i < numFrames; i++) {
const start = i * hopSize;
const frame = signal.slice([start], [frameSize]);
// Apply window and compute RFFT
const windowedFrame = frame.mul(window);
const fft = tf.spectral.rfft(windowedFrame);
frames.push(fft);
}
// Stack frames into a 2D tensor [numFrames, fftLength]
const spectrogram = tf.stack(frames);
return spectrogram;
});
}
const segment_size = 256
const fftSize = 7680
const hop = 1024
const chunk_size = hop * ( segment_size - 1 )
const trim = Math.floor( fftSize / 2 )
const generated_size = chunk_size - 2 * trim
const step = chunk_size - fftSize
const init = async args =>{
await tf.setBackend( 'webgpu' )
await tf.ready()
self.postMessage ({ selector : "init" })
}
const get_spec = async args =>{
const spec = tf.tidy(()=>{
const tensor_l = tf.tensor( args.audio[ 0 ])
const padded_tensor_l = tensor_l.pad([[ trim, trim ]])
const complex_l = stft( padded_tensor_l, fftSize, hop )
const reals_l = tf.real( complex_l )
const imags_l = tf.imag( complex_l )
const tensor_r = tf.tensor( args.audio[ 1 ])
const padded_tensor_r = tensor_r.pad([[ trim, trim ]])
const complex_r = stft( padded_tensor_r, fftSize, hop )
const reals_r = tf.real( complex_r )
const imags_r = tf.imag( complex_r )
real_l = reals_l.slice([ 0, 0 ], [ 256, 3072 ]).transpose([ 1, 0 ])
imag_l = imags_l.slice([ 0, 0 ], [ 256, 3072 ]).transpose([ 1, 0 ])
real_r = reals_r.slice([ 0, 0 ], [ 256, 3072 ]).transpose([ 1, 0 ])
imag_r = imags_r.slice([ 0, 0 ], [ 256, 3072 ]).transpose([ 1, 0 ])
const stacked = tf.stack([ real_l, imag_l, real_r, imag_r ], 1 )
const expanded = stacked.expandDims( 0 )
const transposed = expanded.transpose([ 0, 2, 1, 3 ])
return transposed.dataSync()
})
self.postMessage({ selector : "audio_2_spec", index : args.index, spec })
}
const get_audio = async args =>{
const audio = tf.tidy(()=>{
const output = tf.tensor( args.spec, [ 1, 4, 3072, 256 ] )
const source1Real = output.slice([ 0, 0, 0, 0 ], [ 1, 1, 3072, 256 ]).squeeze([ 0 ])
const source1Imag = output.slice([ 0, 1, 0, 0 ], [ 1, 1, 3072, 256 ]).squeeze([ 0 ])
const source2Real = output.slice([ 0, 2, 0, 0 ], [ 1, 1, 3072, 256 ]).squeeze([ 0 ])
const source2Imag = output.slice([ 0, 3, 0, 0 ], [ 1, 1, 3072, 256 ]).squeeze([ 0 ])
//const totalLength = mix[ 0 ].length - trim
const chunkLength = ( 256 - 1 ) * hop + fftSize
const chunkSignal = new Float32Array( chunkLength )
const vocals_l = new Float32Array( chunkLength )
const vocals_r = new Float32Array( chunkLength )
const hannWindow = create_hann( fftSize )
for( let source of [
{ real : source1Real, imag : source1Imag, buffer : vocals_l },
{ real : source2Real, imag : source2Imag, buffer : vocals_r }
]){
const realFull = source.real.pad([[ 0, 0 ], [ 0, 3841 - 3072 ], [ 0, 0 ]])
const imagFull = source.imag.pad([[ 0, 0 ], [ 0, 3841 - 3072 ], [ 0, 0 ]])
const realFrames = realFull.transpose([ 0, 2, 1 ])
const imagFrames = imagFull.transpose([ 0, 2, 1 ])
for( let t = 0; t < 256; ++t ){
const realFrame = realFrames.gather( t, 1 )
const imagFrame = imagFrames.gather( t, 1 )
const stftFrame = tf.complex( realFrame, imagFrame )
const timeFrame = tf.spectral.irfft( stftFrame )
// Apply Hann window
const windowedFrame = timeFrame.mul(hannWindow);
const frameData = windowedFrame.dataSync();
// Calculate the offset and accumulate the frame data into chunkSignal
const offset = t * hop
for( let j = 0; j < 7680; ++j )
chunkSignal[ offset + j ] += frameData[ j ]
// Clean up
realFrame.dispose();
imagFrame.dispose();
stftFrame.dispose();
timeFrame.dispose();
windowedFrame.dispose();
}
// Trim the chunkSignal to remove unwanted edges
const trimmed = chunkSignal.slice( 3840, chunkLength - 3840 )
// Accumulate into source.buffer
//for( let j = 0; j < trimmed.length && /*i +*/ j < totalLength; ++j )
for( let j = 0; j < trimmed.length && /*i +*/ j < chunkLength; ++j )
source.buffer[ /*i +*/ j ] += trimmed[ j ]
}
return [ vocals_l, vocals_r ]
})
self.postMessage({ selector : "spec_2_audio", index : args.index, audio })
}
self.onmessage = function( event ){
switch( event.data.selector ){
case "init" : init({ model_array_buffer : event.data.model_array_buffer }); break
case "audio_2_spec": get_spec({ index : event.data.index, audio : event.data.audio }); break
case "spec_2_audio": get_audio({ index : event.data.index, spec : event.data.spec }); break
}
}