Spaces:
Running
Running
| <html lang = "en"> | |
| <head> | |
| <meta charset = "UTF-8"> | |
| <meta name = "viewport" content = "width=device-width, initial-scale=1.0"> | |
| <title>demix & workers v250317</title> | |
| </head> | |
| <body style = "margin : 0;"> | |
| <input type = "file" id = "file_input" accept = "audio/mp3" disabled> | |
| <br> | |
| <button id = "stop_button" disabled>stop</button> | |
| <br> | |
| <div id = "audios_div" hidden> | |
| vocals | |
| <br> | |
| <audio id = "vocals_audio" controls></audio> | |
| <br> | |
| instrumentals | |
| <br> | |
| <audio id = "instrumentals_audio" controls></audio> | |
| <br> | |
| </div> | |
| <div id = "progress_div"></div> | |
| <script> | |
| 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 | |
| let file | |
| let started | |
| let mix | |
| let step_count | |
| let ops = 0 | |
| let audio_context | |
| let sample_rate | |
| let converter_worker | |
| let inference_workers | |
| let inference_idle_workers | |
| // inference_workers_count | |
| // determines the number of threads | |
| // larger values operates faster | |
| // but the cpu can overheat | |
| let inference_workers_count = 2 | |
| let model_array_buffer | |
| let mix_specs | |
| let model_specs | |
| let audios | |
| let vocals | |
| let instrumentals | |
| // called everytime progress is reported | |
| const progress =( percent_completed )=>{ | |
| progress_div.innerHTML += "progress = " + Math.floor( percent_completed ) + "%<br>" | |
| } | |
| // called when all operations are complete | |
| const complete = args =>{ | |
| file_input.disabled = false | |
| stop_button.disabled = true | |
| audios_div.hidden = false | |
| progress_div.innerHTML += "audio separation completed in " + (( Date.now() - started )/ 1000 ).toFixed( 0 ) + " seconds.<br>" | |
| } | |
| const create_workers =() =>{ | |
| converter_worker = new Worker( "converter_worker.js" ) | |
| converter_worker.onmessage = function( event ){ | |
| switch( event.data.selector ){ | |
| case "init" : break | |
| case "audio_2_spec":{ | |
| update_progress() | |
| const index = event.data.index | |
| mix_specs[ index ].value = event.data.spec | |
| mix_specs[ index ].state = "complete" | |
| progress_div.innerHTML += "audio_2_spec complete, " + index + " => " + (( Date.now() - started )/ 1000 ).toFixed( 1 ) + "<br>" | |
| schedule() | |
| } break | |
| case "spec_2_audio":{ | |
| update_progress() | |
| const index = event.data.index | |
| audios[ 0 ][ index ].value = event.data.audio[ 0 ] | |
| audios[ 0 ][ index ].state = "complete" | |
| audios[ 1 ][ index ].value = event.data.audio[ 1 ] | |
| audios[ 1 ][ index ].state = "complete" | |
| progress_div.innerHTML += "spec_2_audio complete, " + index + " => " + (( Date.now() - started )/ 1000 ).toFixed( 1 ) + "<br>" | |
| schedule() | |
| } break | |
| } | |
| } | |
| converter_worker.postMessage({ selector : "init" }) | |
| inference_workers =[] | |
| inference_idle_workers =[] | |
| for( let i = 0; i < inference_workers_count; ++i ){ | |
| const inference_worker = new Worker( "inference_worker.js" ) | |
| inference_worker.onmessage = function( event ){ | |
| switch( event.data.selector ){ | |
| case "init" : break | |
| case "run_model":{ | |
| update_progress() | |
| const index = event.data.index | |
| model_specs[ index ].value = event.data.spec | |
| model_specs[ index ].state = "complete" | |
| inference_idle_workers.push( inference_workers[ event.data.id ]) | |
| progress_div.innerHTML += "model_run complete, " + index + " => " + (( Date.now() - started )/ 1000 ).toFixed( 1 ) + "<br>" | |
| schedule() | |
| } break | |
| } | |
| } | |
| inference_worker.postMessage({ selector : "init", id : i, model_array_buffer }) | |
| inference_workers.push( inference_worker ) | |
| } | |
| inference_idle_workers = inference_workers.slice() | |
| } | |
| const construct =() =>{ | |
| const vocals_size = mix[ 0 ].length | |
| vocals = [ new Float32Array( vocals_size ), new Float32Array( vocals_size )] | |
| instrumentals = [ new Float32Array( vocals_size ), new Float32Array( vocals_size )] | |
| let offset = 0 | |
| for( const audio of audios[ 0 ] ){ | |
| for( const [ index, value ] of audio.value.entries()){ | |
| vocals[ 0 ][ index + offset ] += value * .095 | |
| instrumentals[ 0 ][ index + offset ] = mix[ 0 ][ index + offset ] - vocals[ 0 ][ index + offset ] | |
| } | |
| offset += audio.value.length - trim * 4 | |
| } | |
| vocals_audio.src = URL.createObjectURL( array_2_wav( vocals[ 0 ], sample_rate )) | |
| instrumentals_audio.src = URL.createObjectURL( array_2_wav( instrumentals[ 0 ], sample_rate )) | |
| complete() | |
| } | |
| const schedule =() =>{ | |
| let completed = 0 | |
| for( let i = 0; i < audios[ 0 ].length; ++i ) | |
| if( audios[ 0 ][ i ].state == "complete" ) | |
| ++completed | |
| if( completed == step_count ){ | |
| construct() | |
| return | |
| } | |
| // scan for pending inferences | |
| for( let i = 0; i < model_specs.length; ++i ) | |
| if( model_specs[ i ].state == "pending" && mix_specs[ i ].state == "complete" && inference_idle_workers.length ){ | |
| const inference_worker = inference_idle_workers[ 0 ] | |
| inference_idle_workers.shift() | |
| model_specs[ i ].state = "wip" | |
| inference_worker.postMessage({ selector : "run_model", index : i, spec : mix_specs[ i ].value }) | |
| progress_div.innerHTML += "run_model, index = " + i + "<br>" | |
| break | |
| } | |
| // scan for pending mix_specs | |
| for( let i = 0; i < mix_specs.length; ++i ) | |
| if( mix_specs[ i ].state == "pending" ){ | |
| mix_specs[ i ].state = "wip" | |
| converter_worker.postMessage({ selector : "audio_2_spec", index : i, audio : get_mix_part( i * step )}) | |
| progress_div.innerHTML += "audio_2_spec, index = " + i + "<br>" | |
| return | |
| } | |
| // scan for pending audios | |
| for( let i = 0; i < audios[ 0 ].length; ++i ) | |
| if( audios[ 0 ][ i ].state == "pending" && model_specs[ i ].state == "complete" ){ | |
| audios[ 0 ][ i ].state = "wip" | |
| converter_worker.postMessage({ selector : "spec_2_audio", index : i, spec : model_specs[ i ].value }) | |
| progress_div.innerHTML += "spec_2_audio, index = " + i + "<br>" | |
| break | |
| } | |
| } | |
| const get_mix_part = index =>{ | |
| const end = Math.min( index + chunk_size, mix[ 0 ].length ) | |
| mix_part = mix.map( channel => channel.slice( index, end )) | |
| if( end !== index + chunk_size ) | |
| mix_part = mix_part.map( channel => new Float32Array([ ... channel, ... new Float32Array( index + chunk_size - end )])) | |
| return mix_part | |
| } | |
| const update_progress =() =>{ | |
| const percent_completed = ++ops * 100 / ( step_count * 3 ) | |
| progress ( percent_completed ) | |
| } | |
| const array_2_wav =( audioArray, sampleRate = 44100 ) =>{ | |
| function writeString(view, offset, string){ | |
| for( let i = 0; i < string.length; ++i ) | |
| view.setUint8( offset + i, string.charCodeAt( i )) | |
| } | |
| const buffer = new ArrayBuffer(44 + audioArray.length * 2); | |
| const view = new DataView(buffer); | |
| writeString(view, 0, 'RIFF'); | |
| view.setUint32(4, 36 + audioArray.length * 2, true); | |
| writeString(view, 8, 'WAVE'); | |
| writeString(view, 12, 'fmt '); | |
| view.setUint32(16, 16, true); | |
| view.setUint16(20, 1, true); | |
| view.setUint16(22, 1, true); | |
| view.setUint32(24, sampleRate, true); | |
| view.setUint32(28, sampleRate * 2, true); | |
| view.setUint16(32, 2, true); | |
| view.setUint16(34, 16, true); | |
| writeString(view, 36, 'data'); | |
| view.setUint32(40, audioArray.length * 2, true); | |
| for (let i = 0; i < audioArray.length; i++) { | |
| const sample = Math.max(-1, Math.min(1, audioArray[i])); | |
| view.setInt16(44 + i * 2, sample * 0x7FFF, true); | |
| } | |
| return new Blob([ buffer ], { type: 'audio/wav' }) | |
| } | |
| const play =( array, sample_rate = 44100 ) =>{ | |
| const buffer = audio_context.createBuffer( 1, array.length, sample_rate ) | |
| buffer.getChannelData( 0 ).set( array ) | |
| const buffer_source = audio_context.createBufferSource() | |
| buffer_source.buffer = buffer | |
| buffer_source.connect( audio_context.destination ) | |
| buffer_source.start () | |
| } | |
| const download =( filename, array, sample_rate = 44100 )=>{ | |
| const a = document.createElement( 'a' ) | |
| a.href = URL.createObjectURL( array_2_wav( array, sample_rate )) | |
| a.download = filename | |
| a.click () | |
| } | |
| async function start(){ | |
| started = Date.now() | |
| progress_div.innerHTML = "starting ..." + "<br>" | |
| file_input.disabled = true | |
| stop_button.disabled = false | |
| audios_div.hidden = true | |
| const array_buffer = await file.arrayBuffer() | |
| const audio_buffer = await audio_context.decodeAudioData( array_buffer ) | |
| sample_rate = audio_buffer.sampleRate | |
| mix =[] | |
| for( let channel = 0; channel < audio_buffer.numberOfChannels; ++channel ){ | |
| const channel_data = audio_buffer.getChannelData( channel ) | |
| const pad = generated_size + trim - ( channel_data.length % generated_size ) | |
| mix.push ( new Float32Array([ ... new Float32Array( trim ), ... channel_data, ... new Float32Array( pad )])) | |
| } | |
| step_count = Math.floor( mix[ 0 ].length / step ) + 1 | |
| audios = [ Array.from({ length : step_count }, () =>({ state : "pending", value : null })), | |
| Array.from({ length : step_count }, () =>({ state : "pending", value : null }))] | |
| mix_specs = Array.from({ length : step_count }, () =>({ state : "pending", value : null })) | |
| model_specs = Array.from({ length : step_count }, () =>({ state : "pending", value : null })) | |
| schedule() | |
| } | |
| ( async ()=>{ | |
| audio_context = new AudioContext() | |
| const fetched = await fetch( "UVR_MDXNET_Main.onnx" ) | |
| model_array_buffer = await fetched.arrayBuffer() | |
| create_workers () | |
| file_input.disabled = false | |
| })() | |
| document.getElementById( 'file_input' ).addEventListener( 'change', function(){ | |
| file = file_input.files[ 0 ] | |
| start() | |
| }) | |
| document.getElementById( 'stop_button' ).addEventListener( 'click', function(){ | |
| stop_button.disabled = true | |
| converter_worker.terminate() | |
| for( const worker of inference_workers ) | |
| worker.terminate() | |
| create_workers() | |
| }) | |
| </script> | |
| </body> | |
| </html> | |