(function( PKAE ) { 'use strict'; function AudioUtils ( master, wavesurfer ) { // audio destination var audio_destination = wavesurfer.backend.analyser; var audio_ctx = wavesurfer.backend.ac; var audio_script_node = audio_ctx.createScriptProcessor(256); function loadDecoded ( new_buffer ) { wavesurfer.loadDecodedBuffer ( new_buffer ); master.fireEvent ('DidUpdateLen', wavesurfer.getDuration ()); }; function OverwriteBufferWithSegment (_offset, _duration, withBuffer ) { var originalBuffer = wavesurfer.backend.buffer; TrimBuffer( _offset, _duration, true ); var ret = InsertSegmentToBuffer ( _offset, withBuffer ); setTimeout (function() { wavesurfer.drawBuffer(); },40); return (ret); } function OverwriteBuffer ( withBuffer ) { loadDecoded ( withBuffer ); setTimeout (function() { wavesurfer.drawBuffer(); },40); } function MakeSilenceBuffer ( _duration ) { var originalBuffer = wavesurfer.backend.buffer; var emptySegment = wavesurfer.backend.ac.createBuffer( originalBuffer.numberOfChannels, _duration * originalBuffer.sampleRate, originalBuffer.sampleRate ); return (emptySegment); } function CopyBufferSegment( _offset, _duration ) { var originalBuffer = wavesurfer.backend.buffer; var new_len = ((_duration/1) * originalBuffer.sampleRate) >> 0; var new_offset = ((_offset/1) * originalBuffer.sampleRate) >> 0; var emptySegment = wavesurfer.backend.ac.createBuffer ( wavesurfer.SelectedChannelsLen, new_len, originalBuffer.sampleRate ); for (var i = 0, u = 0; i < wavesurfer.ActiveChannels.length; ++i) { if (wavesurfer.ActiveChannels[ i ] === 0) continue; emptySegment.getChannelData ( u ).set ( originalBuffer.getChannelData ( i ).slice ( new_offset, new_len + new_offset ) ); ++u; } return (emptySegment); }; function TrimBuffer( _offset, _duration, force ) { var originalBuffer = wavesurfer.backend.buffer; var new_len = ((_duration/1) * originalBuffer.sampleRate) >> 0; var new_offset = ((_offset/1) * originalBuffer.sampleRate) >> 0; var emptySegment = wavesurfer.backend.ac.createBuffer ( !force ? wavesurfer.SelectedChannelsLen : originalBuffer.numberOfChannels, new_len, originalBuffer.sampleRate ); var uberSegment = null; if (!force && wavesurfer.SelectedChannelsLen < originalBuffer.numberOfChannels) { uberSegment = wavesurfer.backend.ac.createBuffer ( originalBuffer.numberOfChannels, originalBuffer.length, originalBuffer.sampleRate ); for (var i = 0; i < originalBuffer.numberOfChannels; ++i) { var chan_data = originalBuffer.getChannelData ( i ); var uber_chan_data = uberSegment.getChannelData ( i ); if (wavesurfer.ActiveChannels[ i ] === 0) { uber_chan_data.set ( chan_data ); } else { var segment_chan_data = emptySegment.getChannelData (0); segment_chan_data.set ( chan_data.slice ( new_offset, new_offset + new_len ) ); uber_chan_data.set ( chan_data.slice ( 0, new_offset ) ); uber_chan_data.set ( chan_data.slice ( new_offset + new_len ), new_offset + new_len ); } } } else { uberSegment = wavesurfer.backend.ac.createBuffer( originalBuffer.numberOfChannels, originalBuffer.length - new_len, originalBuffer.sampleRate ); for (var i = 0; i < originalBuffer.numberOfChannels; ++i) { var chan_data = originalBuffer.getChannelData(i); var segment_chan_data = emptySegment.getChannelData(i); var uber_chan_data = uberSegment.getChannelData(i); segment_chan_data.set ( chan_data.slice ( new_offset, new_offset + new_len ) ); uber_chan_data.set ( chan_data.slice ( 0, new_offset ) ); uber_chan_data.set ( chan_data.slice ( new_offset + new_len ), new_offset ); } } loadDecoded ( uberSegment, originalBuffer ); return (emptySegment); }; function InsertSegmentToBuffer( _offset, buffer ) { var originalBuffer = wavesurfer.backend.buffer; var uberSegment = wavesurfer.backend.ac.createBuffer( originalBuffer.numberOfChannels, originalBuffer.length + buffer.length, originalBuffer.sampleRate ); _offset = ((_offset / 1) * originalBuffer.sampleRate) >> 0; for (var i = 0; i < originalBuffer.numberOfChannels; ++i) { var chan_data = originalBuffer.getChannelData( i ); var uberChanData = uberSegment.getChannelData( i ); var segment_chan_data = null; if (buffer.numberOfChannels === 1) segment_chan_data = buffer.getChannelData( 0 ); else segment_chan_data = buffer.getChannelData( i ); // check to see if we have only 1 channel selected if (wavesurfer.SelectedChannelsLen === 1) { // check if we have the selected channel if (wavesurfer.ActiveChannels[ i ] === 0) { // keep original uberChanData.set ( chan_data ); continue; } } if (_offset > 0) { uberChanData.set ( chan_data.slice ( 0, _offset ) ); } uberChanData.set ( segment_chan_data, _offset ); if (_offset < (originalBuffer.length + buffer.length) ) { uberChanData.set ( chan_data.slice( _offset ), _offset + segment_chan_data.length ); } } loadDecoded ( uberSegment, originalBuffer ); return [ (_offset / originalBuffer.sampleRate), (_offset / originalBuffer.sampleRate) + (buffer.length / originalBuffer.sampleRate) ]; }; function ReplaceFloatArrays ( _offset, arrays ) { var originalBuffer = wavesurfer.backend.buffer; var arr_len = arrays.length; var arr_samples = arrays[0].length; var new_len = (arr_samples * arr_len); var buff_len = originalBuffer.length; _offset = ((_offset / 1) * originalBuffer.sampleRate) >> 0; if (buff_len < (_offset + new_len)) { buff_len = (_offset + new_len); } var uberSegment = wavesurfer.backend.ac.createBuffer ( originalBuffer.numberOfChannels, buff_len, originalBuffer.sampleRate ); for (var i = 0; i < originalBuffer.numberOfChannels; i++) { var chan_data = originalBuffer.getChannelData( i ); var uberChanData = uberSegment.getChannelData( i ); if (_offset > 0) { uberChanData.set ( chan_data.slice ( 0, _offset ) ); } for (var j = 0; j < arr_len; ++j) { uberChanData.set ( arrays[ j ], _offset + (j * arr_samples) ); } if (_offset < (originalBuffer.length + new_len) ) { uberChanData.set ( chan_data.slice( _offset + new_len ), _offset + new_len ); } } loadDecoded ( uberSegment, originalBuffer ); return [ (_offset / originalBuffer.sampleRate), (_offset / originalBuffer.sampleRate) + (new_len / originalBuffer.sampleRate) ]; }; function InsertFloatArrays( _offset, arrays ) { var originalBuffer = wavesurfer.backend.buffer; var arr_len = arrays.length; var arr_samples = arrays[0].length; var new_len = (arr_samples * arr_len); _offset = ((_offset / 1) * originalBuffer.sampleRate) >> 0; var uberSegment = wavesurfer.backend.ac.createBuffer( originalBuffer.numberOfChannels, originalBuffer.length + new_len, originalBuffer.sampleRate ); for (var i = 0; i < originalBuffer.numberOfChannels; i++) { var chan_data = originalBuffer.getChannelData( i ); var uberChanData = uberSegment.getChannelData( i ); if (_offset > 0) { uberChanData.set ( chan_data.slice ( 0, _offset ) ); } for (var j = 0; j < arr_len; ++j) { uberChanData.set ( arrays[ j ], _offset + (j * arr_samples) ); } if (_offset < (originalBuffer.length + new_len) ) { uberChanData.set ( chan_data.slice( _offset ), _offset + new_len ); } } loadDecoded ( uberSegment, originalBuffer ); return [ (_offset / originalBuffer.sampleRate), (_offset / originalBuffer.sampleRate) + (new_len / originalBuffer.sampleRate) ]; }; function getAudioContext() { if (!window.WaveSurferAudioContext) { window.WaveSurferAudioContext = new (window.AudioContext || window.webkitAudioContext)(); } return window.WaveSurferAudioContext; } function getOfflineAudioContext (channels, sampleRate, duration) { return new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(channels, duration, sampleRate); }; function initPreview (val) { this.previewVal = val; }; function stopPreview (_fx) { if (!this.previewing) return ; if (_fx) { _fx.destroy && _fx.destroy (); } if (this.PreviewFilter) { if (this.PreviewFilter.length > 0) { for (var ii = 0; ii < this.PreviewFilter.length; ++ii) this.PreviewFilter[ ii ].disconnect (); } else this.PreviewFilter.disconnect (); } var script_node = audio_script_node; // wavesurfer.backend.scriptNode script_node.disconnect (); wavesurfer.backend.scriptNode.connect (audio_ctx.destination); // wavesurfer.backend.scriptNode.connect (audio_ctx.destination); // wavesurfer.backend.scriptNode.onaudioprocess = null; this.PreviewSource.stop(); this.PreviewSource.disconnect (); this.PreviewDestination = this.PreviewSource = this.PreviewFilter = this.PreviewUpdate = null; this.previewing = 0; } function togglePreview () { if (!this.previewing) { this.previewVal = !this.previewVal; return (this.previewVal); } if (this.previewing === 2) { // if (this.PreviewFilter) // { // if (this.PreviewFilter.length > 0) // { // for (var ii = 0; ii < this.PreviewFilter.length; ++ii) // this.PreviewFilter[ ii ].disconnect (); // } // else // this.PreviewFilter.disconnect (); // } if (this.PreviewTog) { this.PreviewTog (false, this.PreviewSource); } this.PreviewSource.disconnect (); this.PreviewSource.connect (this.PreviewDestination); this.previewing = 1; this.previewVal = false; return (false); } else { this.PreviewSource.disconnect (); if (this.PreviewFilter) { if (this.PreviewFilter.length > 0) { !this.PreviewFilter[ 0 ].buffer && this.PreviewSource.connect (this.PreviewFilter[ 0 ]); // var ii = 0; // for (; ii < this.PreviewFilter.length - 1; ++ii) // { // this.PreviewFilter[ ii ].disconnect (); // this.PreviewFilter[ ii ].connect (this.PreviewFilter[ ii + 1 ]); // } // this.PreviewFilter[ ii ].connect (this.PreviewDestination); } else { !this.PreviewFilter.buffer && this.PreviewSource.connect (this.PreviewFilter); this.PreviewFilter.disconnect (); this.PreviewFilter.connect (this.PreviewDestination); } } if (this.PreviewTog) { this.PreviewTog (true, this.PreviewSource); } this.previewing = 2; this.previewVal = true; return (true); } } function previewEffect ( _offset, _duration, _fx ) { if (this.previewing) stopPreview (_fx); var orig_buffer = wavesurfer.backend.buffer; if (!_offset && !_duration) { _offset = 0; _duration = (orig_buffer.length / orig_buffer.sampleRate) >> 0; } var script_node = audio_script_node; //wavesurfer.backend.scriptNode; var fx_buffer = CopyBufferSegment (_offset, _duration); var audio_ctx = wavesurfer.backend.ac || getAudioContext (); var source = audio_ctx.createBufferSource (); source.buffer = fx_buffer; source.loop = true; this.PreviewFilter = this.PreviewTog = null; if (!_fx) source.connect (audio_destination); else { this.PreviewTog = _fx.preview; this.PreviewUpdate = _fx.update; this.PreviewFilter = _fx.filter ( audio_ctx, audio_destination, source, _duration/1 ); } script_node.disconnect (); wavesurfer.backend.scriptNode.disconnect (); script_node.connect( audio_ctx.destination ); var skipp = 1; var prev_fft = 0; var dataArray = null; script_node.onaudioprocess = ( e ) => { var loudness = [0, 0]; var temp = 0; // var flip = false; --skipp; if (skipp === 0) { if (audio_destination.getFloatTimeDomainData) { if (prev_fft !== audio_destination.fftSize) { dataArray = new Float32Array(audio_destination.fftSize); // Float32Array needs to be the same length as the fftSize prev_fft = audio_destination.fftSize; } audio_destination.getFloatTimeDomainData (dataArray); // fill the Float32Array with data returned from getFloatTimeDomainData() for (var j = 0; j < audio_destination.fftSize; j += 1) { var x = dataArray[j]; if (Math.abs(x) >= temp) { temp = Math.abs(x); } } loudness[0] = 20 * Math.log10(temp) + 0.001; } else { if (prev_fft !== audio_destination.fftSize) { dataArray = new Uint8Array(audio_destination.fftSize); // Float32Array needs to be the same length as the fftSize prev_fft = audio_destination.fftSize; } audio_destination.getByteTimeDomainData (dataArray); // fill the Float32Array with data returned from getFloatTimeDomainData() var total_float = 0; for (var j = 0; j < audio_destination.fftSize; j += 1) { var float = ( dataArray[j] / 0x80 ) - 1; total_float += ( float * float ); } var rms = Math.sqrt (total_float / audio_destination.fftSize); loudness[0] = 20 * ( Math.log(rms) / Math.log(10) ); } if (loudness[0] < -100) loudness[0] = -100; loudness[1] = loudness[0]; // audio_destination.fftSize = 512; audio_destination.getByteFrequencyData( wavesurfer.backend.FreqArr ); //wavesurfer.backend.peak_frequency = Math.max.apply( null, wavesurfer.backend.FreqArr ); master.fireEvent ('DidAudioProcess',[-1, loudness, e.timeStamp], wavesurfer.backend.FreqArr); // wavesurfer.backend.peak_frequency = [0, 0]; skipp = 2; } }; source.start (); this.PreviewSource = source; this.PreviewDestination = audio_destination; this.previewing = 2; if (!this.previewVal) { togglePreview.call (this); } return (source); } function applyEffect( _offset, _duration, _fx ) { var orig_buffer = wavesurfer.backend.buffer; if (!_offset && !_duration) { _offset = 0; _duration = (orig_buffer.length / orig_buffer.sampleRate) >> 0; } if (_offset <0) _offset = 0; if (wavesurfer.getDuration () < _duration) _duration = wavesurfer.getDuration (); var fx_buffer = CopyBufferSegment ( _offset, _duration ); var new_offset = ((_offset/1) * orig_buffer.sampleRate) >> 0; var audio_ctx = getOfflineAudioContext ( wavesurfer.SelectedChannelsLen, // orig_buffer.numberOfChannels, orig_buffer.sampleRate, fx_buffer.length ); var source = audio_ctx.createBufferSource (); source.buffer = fx_buffer; var filter = null; if (_fx) { filter = _fx.filter ( audio_ctx, audio_ctx.destination, source, _duration/1 ); filter.destroy && filter.destroy (); } source.start (); var offline_callback = function( rendered_buffer ) { var uber_buffer = wavesurfer.backend.ac.createBuffer( orig_buffer.numberOfChannels, orig_buffer.length, orig_buffer.sampleRate ); for (var i = 0; i < orig_buffer.numberOfChannels; ++i) { var uber_chan_data = uber_buffer.getChannelData (i); var chan_data = orig_buffer.getChannelData (i); // check if channel is active if (wavesurfer.ActiveChannels[ i ] === 0) { uber_chan_data.set ( chan_data ); continue; } var fx_chan_data = null; if (rendered_buffer.numberOfChannels === 1) fx_chan_data = rendered_buffer.getChannelData( 0 ); else fx_chan_data = rendered_buffer.getChannelData( i ); uber_chan_data.set ( chan_data ); uber_chan_data.set ( fx_chan_data, new_offset, fx_chan_data.length - new_offset ); } loadDecoded ( uber_buffer ); if (filter.length > 0) { for (var i = 0; i < filter.length; ++i) filter[i].disconnect (); } else filter && filter.disconnect && filter.disconnect (); // is this needed? rendered_buffer = fx_buffer = filter = null; source.disconnect (); // audio_ctx.close (); // - }; var offline_renderer = audio_ctx.startRendering(); if (offline_renderer) offline_renderer.then( offline_callback ).catch(function(err) { console.log('Rendering failed: ' + err); }); else audio_ctx.oncomplete = function ( e ) { offline_callback ( e.renderedBuffer ); }; }; /* /////////////// ----------------------- // ATTEMPTING BACKGROUND NOISE REMOVAL function findTopFrequencies (_offset, _duration, callback) { var q = this; var audio_ctx = getAudioContext (); var buffer_source = audio_ctx.createBufferSource(); buffer_source.buffer = CopyBufferSegment (_offset, _duration); var analyser = audio_ctx.createAnalyser (); analyser.fftSize = 2048; analyser.minDecibelsis = -40; analyser.maxDecibelsis = 0; var scp = audio_ctx.createScriptProcessor (256, 0, 1); buffer_source.connect (analyser); scp.connect (audio_ctx.destination); // buffer_source.loop = true; var samples = 0; var finger_print = new Uint16Array (analyser.frequencyBinCount); var freq_data = new Uint8Array (analyser.frequencyBinCount); scp.onaudioprocess = function () { analyser.getByteFrequencyData (freq_data); for (var i = 0; i < freq_data.length; ++i) { finger_print[ i ] += freq_data [ i ]; } ++samples; }; buffer_source.onended = function() { var sampleRate = buffer_source.buffer.sampleRate; buffer_source.stop (); buffer_source.disconnect (); scp.disconnect (); for (var i = 0; i < finger_print.length; ++i) { finger_print[ i ] /= samples >> 0; if (finger_print[ i ] < 10) { finger_print[ i ] = 0; } } callback && callback.apply (q, [finger_print] ); }; buffer_source.start (0); } function killdTopFrequencies (_offset, _duration, _noise_profile) { var q = this; var step = function ( _offset, _duration, callback ) { findTopFrequencies (_offset, _duration, function( frequencies ) { var similarity = []; var similar_frequencies = 0; for (var i = 0; i < _noise_profile.length; ++i) { var val = Math.abs ( frequencies[ i ] - _noise_profile[ i ] ); if (val < 10) { similarity[ i ] = (val == 0 && _noise_profile[ i ] > 0) ? 5 : val; ++similar_frequencies; } } if ( similar_frequencies > _noise_profile.length / 3) { cleanUpSpecificAudioRange.apply (q, [_offset, _duration, similarity]); } callback && callback (); }); }; if (_duration <= 0.1) step ( _offset, _duration ); else { var new_offset = _offset; var goal = _offset + _duration; var test = function () { if ( new_offset < goal ) { var dur_step = 0.1; if (new_offset + dur_step > goal) dur_step = goal - new_offset; step ( new_offset, dur_step, test ); new_offset += dur_step; } } test (); // - } } function cleanUpSpecificAudioRange (_offset, _duration, _frequencies) { // var fx_buffer = CopyBufferSegment (_offset, _duration); var val = []; var all_ok = false; for (var i = 0; i < _frequencies.length; ++i) { if (!_frequencies[i]) continue; val.push({ 'type' : 'notch', 'freq' : (i * wavesurfer.backend.ac.sampleRate/_frequencies.length)/2, 'val' : -35,//(_frequencies[i]), 'q' : 10.0 }); all_ok = true; } if (all_ok) { var ff = this.FXBank.ParametricEQ( val ); this.FX( _offset, _duration, ff ); } */ /* var bands = []; var len = val.length; var makeEQ = function ( band ) { var eq = audio_ctx.createBiquadFilter (); eq.type = band.type; eq.gain.value = ~~band.val; eq.Q.value = 1; eq.frequency.value = band.freq; return (eq); }; var eq = makeEQ ( val [0] ); bands.push ( eq ); source.connect (eq); for (var i = 1; i < len - 1; ++i) { eq = makeEQ ( val [ i ] ); bands [ i - 1 ].connect ( eq ); bands.push ( eq ); } eq = makeEQ ( val [ len - 1 ] ); bands [ bands.length - 1 ].connect ( eq ); bands.push ( eq ); eq.connect (audio_ctx.destination); return (bands); */ // } // ENDOF ATTEMPTING BACKGROUND NOISE REMOVAL /////////////// ----------------------- var worker = null; function DownloadFileCancel () { if (worker) { worker.terminate (); worker = null; } } function DownloadFile( with_name, format, kbps, selection, stereo, callback ) { if (wavesurfer && wavesurfer.backend && wavesurfer.backend.buffer){} else { return false; } if (format === 'mp3') { worker = new Worker('lame.js'); } else if (format === 'flac') { worker = new Worker('flac.js'); } else { worker = new Worker('wav.js'); } var originalBuffer = wavesurfer.backend.buffer; var sample_rate = originalBuffer.sampleRate; var channels = originalBuffer.numberOfChannels; var data_left = originalBuffer.getChannelData ( 0 ); var data_right = null; if (channels === 2) data_right = originalBuffer.getChannelData ( 1 ); if (!stereo && channels === 2) { if (!wavesurfer.ActiveChannels[0] && wavesurfer.ActiveChannels[1]) { data_left = originalBuffer.getChannelData ( 1 ); data_right = null; channels = 1; } } if (stereo && !data_right) { data_right = data_left; channels = 2; } else if (!stereo && data_right) { data_right = null; channels = 1; } var len = data_left.length, i = 0; var offset = 0; if (selection) { offset = (selection[0] * sample_rate) >> 0; len = ((selection[1] * sample_rate) >> 0) - offset; } var dataAsInt16ArrayLeft = new Int16Array(len); var dataAsInt16ArrayRight = null; if (data_right) { dataAsInt16ArrayRight = new Int16Array(len); while(i < len) { dataAsInt16ArrayLeft[i] = convert(data_left[offset + i]); dataAsInt16ArrayRight[i] = convert(data_right[offset + i]); ++i; } } else { while(i < len) { dataAsInt16ArrayLeft[i] = convert(data_left[offset + i]); ++i; } } function convert ( n ) { var v = n < 0 ? n * 32768 : n * 32767; // convert in range [-32768, 32767] return Math.max(-32768, Math.min(32768, v)); // clamp } worker.onmessage = function( ev ) { if (ev.data.percentage) { callback && callback ( ev.data.percentage ); return ; } forceDownload( ev.data ); worker.terminate (); worker = null; } worker.postMessage ({ sample_rate: sample_rate, kbps:!kbps ? 128 : kbps, flac_compression: kbps, channels: channels }); worker.postMessage ( dataAsInt16ArrayLeft.buffer, [dataAsInt16ArrayLeft.buffer] ); if (data_right) worker.postMessage ( dataAsInt16ArrayRight.buffer, [dataAsInt16ArrayRight.buffer] ); else worker.postMessage (null); // function forceDownload ( mp3Data ) { // var blob = new Blob (mp3Data, {type:'audio/mp3'}); function forceDownload ( blob ) { var url = (window.URL || window.webkitURL).createObjectURL(blob); var a = document.createElement( 'a' ); a.href = url; a.download = with_name ? with_name : 'output.mp3'; a.style.display = 'none'; document.body.appendChild( a ); a.click(); callback && callback ('done'); } } function updatePreview ( val ) { if (!this.previewing) return ; this.PreviewUpdate && this.PreviewUpdate ( this.PreviewFilter, audio_ctx, val, this.PreviewSource ); } // EFFECTS LOGIC var FXBank = { Gain : function( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { var gain = audio_ctx.createGain (); for (var k = 0; k < val.length; ++k) { var curr = val[k]; if (curr.length) { for (var i = 0; i < curr.length; ++i) { gain.gain.linearRampToValueAtTime (curr[i].val, audio_ctx.currentTime + curr[i].time); } } else { gain.gain.setValueAtTime ( curr.val, audio_ctx.currentTime ); } } gain.connect (destination); source.connect (gain); return (gain); }, update : function ( gain, audio_ctx, val ) { for (var k = 0; k < val.length; ++k) { var curr = val[k]; if (curr.length) { for (var i = 0; i < curr.length; ++i) { gain.gain.linearRampToValueAtTime (curr[i].val, audio_ctx.currentTime + curr[i].time); } } else { gain.gain.setValueAtTime ( curr.val, audio_ctx.currentTime ); } } // ---- } }; }, FadeIn : function( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { var gain = audio_ctx.createGain (); gain.gain.setValueAtTime (0, audio_ctx.currentTime); gain.gain.linearRampToValueAtTime (1, audio_ctx.currentTime + duration/1); gain.connect (destination); source.connect (gain); return (gain); } }; }, FadeOut : function( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { var gain = audio_ctx.createGain (); gain.gain.setValueAtTime (1, audio_ctx.currentTime); gain.gain.linearRampToValueAtTime (0, audio_ctx.currentTime + duration/1); gain.connect (destination); source.connect (gain); return (gain); } }; }, Compressor : function ( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { var compressor = audio_ctx.createDynamicsCompressor (); for (var k in val) { if (val[k].length) { for (var i = 0; i < val[k].length; ++i) { var curr = val[k][i]; compressor[k].linearRampToValueAtTime (curr.val, audio_ctx.currentTime + curr.time); } } else { compressor[k].setValueAtTime ( val[k].val, audio_ctx.currentTime ); } } compressor.connect (destination); source.connect (compressor); return (compressor); }, update : function ( compressor, audio_ctx, val ) { for (var k in val) { if (val[k].length) { for (var i = 0; i < val[k].length; ++i) { var curr = val[k][i]; compressor[k].linearRampToValueAtTime (curr.val, audio_ctx.currentTime + curr.time); } } else { compressor[k].setValueAtTime ( val[k].val, audio_ctx.currentTime ); } } // --- } }; }, Reverse : function ( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { for (var i = 0; i < source.buffer.numberOfChannels; ++i) { Array.prototype.reverse.call( source.buffer.getChannelData (i) ); } source.connect (destination); return (source); }, update : function () {} }; }, Invert : function ( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { for (var i = 0; i < source.buffer.numberOfChannels; ++i) { var channel = source.buffer.getChannelData (i); for (var j = 0; j < channel.length; ++j) channel[j] *= -1; } source.connect (destination); return (source); }, update : function () {} }; }, Flip : function ( val, val2 ) { return { filter : function ( audio_ctx, destination, source, duration ) { if (val === 'flip') { var chan0 = source.buffer.getChannelData (0); var chan1 = source.buffer.getChannelData (1); var tmp = 0; for (var j = 0; j < chan0.length; ++j) { tmp = chan0[j]; chan0[j] = chan1[j]; chan1[j] = tmp; } } source.connect (destination); return (source); }, update : function () {} }; }, Normalize : function ( val ) { //todo ASM JS?? return { filter : function ( audio_ctx, destination, source, duration ) { var max_val = val[1] || 1.0; var equally = val[0]; var max_peak = 0; for (var i = 0; i < source.buffer.numberOfChannels; ++i) { var chan_data = source.buffer.getChannelData (i); // iterating faster first time... for (var k = 1, len = chan_data.length; k < len; k = k + 10) { var curr = Math.abs ( chan_data [ k ] ); if (max_peak < curr) max_peak = curr; } var diff = max_val / max_peak; if (!equally) { for (var k = 0, len = chan_data.length; k < len; ++k) { chan_data[ k ] *= diff; } max_peak = 0; } } if (equally) { var diff = max_val / max_peak; for (var i = 0; i < source.buffer.numberOfChannels; ++i) { var chan_data = source.buffer.getChannelData (i); for (var k = 0, len = chan_data.length; k < len; ++k) { chan_data[ k ] *= diff; } } } source.connect (destination); return (source); }, update : function () {} }; }, HardLimit : function ( val ) { //todo ASM JS?? return { filter : function ( audio_ctx, destination, source, duration ) { var max_val = val[1] || 1.0; var ratio = val[2] || 0.0; var look_ahead = val[3] || 15; // ms var equally = false; //val[0]; var max_peak = 0; var buffer = audio_ctx.createBuffer( source.buffer.numberOfChannels, source.buffer.length, source.buffer.sampleRate ); look_ahead = (look_ahead * buffer.sampleRate/1000) >> 0; for (var i = 0; i < buffer.numberOfChannels; ++i) { var chan_data = buffer.getChannelData (i); chan_data.set ( source.buffer.getChannelData (i) ); // iterating faster first time... for (var b = 0, len = chan_data.length; b < len; ++b) { for (var k = 0; k < look_ahead; k = k + 10) { var curr = Math.abs ( chan_data [ b + k ] ); if (max_peak < curr) max_peak = curr; } var diff = (max_val / max_peak); if (!equally) { for (var k = 0; k < look_ahead; ++k) { var orig_val = chan_data[ b + k ]; var new_val = orig_val * diff; var peak_diff = max_val - Math.abs (new_val); peak_diff *= orig_val < 0 ? -ratio : ratio; chan_data[ b + k ] = (new_val + peak_diff); } b += look_ahead; max_peak = 0; } } // ----- } // todo handle disconnected LEFT AND RIGHT var temp_source = audio_ctx.createBufferSource (); temp_source.buffer = buffer; temp_source.loop = true; temp_source.start (); temp_source.connect (destination); return (temp_source); }, update : function ( filtered_source, audio_ctx, val, source ) { // stop the existing onerror filtered_source.disconnect (); filtered_source.buffer = null; filtered_source = null; var ff = this.FXBank.HardLimit( val ); this.PreviewFilter = ff.filter ( audio_ctx, audio_destination, source, 0 ); } }; }, ParametricEQ : function ( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { var bands = []; var len = val.length; var makeEQ = function ( band ) { var eq = audio_ctx.createBiquadFilter (); if (band.length) { for (var i = 0; i < band.length; ++i) { eq.gain.linearRampToValueAtTime (~~band[i].val, audio_ctx.currentTime + band[i].time); } band = band[0]; } else eq.gain.value = ~~band.val; eq.type = band.type; eq.Q.value = band.q || 1.0; eq.frequency.value = band.freq; return (eq); }; if (!val[0]) { val[0] = { type:'peaking', val:0, q:1, freq:500 }; } var eq = makeEQ ( val[0] ); bands.push ( eq ); source.connect (eq); if (val.length === 1) { eq.connect (destination); return (bands); } for (var i = 1; i < len - 1; ++i) { eq = makeEQ ( val [ i ] ); bands [ i - 1 ].connect ( eq ); bands.push ( eq ); } eq = makeEQ ( val[ len - 1 ] ); bands [ bands.length - 1 ].connect ( eq ); bands.push ( eq ); eq.connect (destination); return (bands); }, update : function ( bands, audio_ctx, val, source ) { if (bands.length !== val.length) { var makeEQ = function ( band ) { var eq = audio_ctx.createBiquadFilter (); return (eq); }; if (bands.length < val.length) { var l = val.length - bands.length; while (l-- > 0) { var eq = makeEQ (); var connect_to = bands[0]; bands.unshift (eq); eq.connect (connect_to); } source.disconnect (); source.connect (bands[0]); } else { if (val.length > 0) { var l = bands.length - val.length; source.disconnect (); for (var i = 0; i < l; ++i) { var eq = bands.shift(); eq.disconnect (); } source.connect (bands[0]); } else { val[0] = { type:'peaking', val:0, q:1, freq:500 }; } } } var len = val.length; for (var i = 0; i < len; ++i) { var eq = bands [ i ]; eq.type = val[ i ].type; eq.gain.value = ~~val[ i ].val; eq.Q.value = val[ i ].q || 1.0; eq.frequency.value = val[ i ].freq; } // - } }; }, Rate : function ( val ) { var prev_val = 1.0; var temp_source = []; return { filter : function ( audio_ctx, destination, source, duration ) { var fx_buffer = source.buffer; var stretch_ratio = val; let grainDuration = 0.05; // 50 ms grain const analysisHop = 0.025; // 25 ms step (50% overlap) const desiredOverlap = 0.5; // 50% overlap const synthesisHop = analysisHop * stretch_ratio; // output hop if (stretch_ratio > 1) { grainDuration = synthesisHop / (1 - desiredOverlap); // 0.15 sec (150 ms } var offlineCtx = audio_ctx; const now = audio_ctx.currentTime; // var filter = fx.filter ( offlineCtx, offlineCtx.destination, null, duration ); var applyHannWindowFast = function (gainNode, outputTime, grainDuration) { // The automation curve using a Hann window shape const numSteps = 50; for (let i = 0; i <= numSteps; i++) { const t = (i / numSteps) * grainDuration; const windowValue = 0.5 * (1 - Math.cos((2 * Math.PI * t) / grainDuration)); gainNode.gain.linearRampToValueAtTime(windowValue, outputTime + t); } }; // Schedule grains var grainIndex = 0; var filter_chain = []; for (let t = 0; t < fx_buffer.duration; t += analysisHop) { const offset = t; const outputTime = grainIndex * synthesisHop; if (offset + grainDuration > fx_buffer.duration) break; // stop if beyond source const grainSource = offlineCtx.createBufferSource(); grainSource.buffer = fx_buffer; const grainGain = offlineCtx.createGain(); grainSource.connect(grainGain); grainGain.connect(offlineCtx.destination); applyHannWindowFast (grainGain, now + outputTime, grainDuration); grainSource.start(now + outputTime, offset, grainDuration); filter_chain.push (grainGain); temp_source[grainIndex] = grainSource; ++grainIndex; } return (filter_chain); }, destroy : function () { temp_source = []; }, update : function ( filter_chain, audio_ctx, val, source ) { prev_val = 1 / val; var fx_buffer = source.buffer; let grainDuration = 0.05; // 50 ms grain const analysisHop = 0.025; // 25 ms step (50% overlap) const desiredOverlap = 0.5; // 50% overlap const synthesisHop = analysisHop * prev_val; // output hop if (prev_val > 1) { grainDuration = synthesisHop / (1 - desiredOverlap); // 0.15 sec (150 ms } const now = audio_ctx.currentTime; // var filter = fx.filter ( offlineCtx, offlineCtx.destination, null, duration ); var applyHannWindowFast = function (gainNode, outputTime, grainDuration) { // The automation curve using a Hann window shape const numSteps = 50; for (let i = 0; i <= numSteps; i++) { const t = (i / numSteps) * grainDuration; const windowValue = 0.5 * (1 - Math.cos((2 * Math.PI * t) / grainDuration)); gainNode.gain.linearRampToValueAtTime(windowValue, outputTime + t); } }; // Schedule grains var l = filter_chain.length; var t = 0; for (var i = 0; i < l; ++i) { const offset = t; const outputTime = i * synthesisHop; //if (offset + grainDuration > fx_buffer.duration) break; const grainGain = filter_chain[i]; let grainSource = temp_source[i]; grainSource.stop(); grainSource = audio_ctx.createBufferSource(); grainGain.gain.setValueAtTime(grainGain.gain.value, now); grainGain.gain.cancelScheduledValues(now); grainSource.buffer = fx_buffer; grainSource.connect(grainGain); temp_source[i] = grainSource; applyHannWindowFast (grainGain, outputTime + now, grainDuration); grainSource.start(now + outputTime, offset, grainDuration); t += analysisHop; } // -- } }; }, Speed : function ( val ) { var prev_val = 1.0; return { filter : function ( audio_ctx, destination, source, duration ) { var inputNode = audio_ctx.createGain(); source.playbackRate.value = val; source.connect (inputNode); // line in to dry mix inputNode.connect (destination); var filter_chain = [ inputNode ]; return (filter_chain); }, preview: function (state, source) { if (!state) source.playbackRate.value = 1.0; else source.playbackRate.value = prev_val; }, update : function ( filter_chain, audio_ctx, val, source ) { prev_val = val; source.playbackRate.value = val; } }; }, Delay : function ( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { var inputNode = audio_ctx.createGain(); var outputNode = audio_ctx.createGain(); var dryGainNode = audio_ctx.createGain(); var wetGainNode = audio_ctx.createGain(); var feedbackGainNode = audio_ctx.createGain(); var delayNode = audio_ctx.createDelay(); source.connect (inputNode); // line in to dry mix inputNode.connect (dryGainNode); // dry line out dryGainNode.connect (outputNode); // feedback loop delayNode.connect (feedbackGainNode); feedbackGainNode.connect (delayNode); // line in to wet mix inputNode.connect (delayNode); // wet out delayNode.connect (wetGainNode); // wet line out wetGainNode.connect (outputNode); outputNode.connect (destination); var filter_chain = [ inputNode, outputNode, dryGainNode, wetGainNode, feedbackGainNode, delayNode ]; if (!val.delay.length) delayNode.delayTime.value = val.delay.val; else { for (var i = 0; i < val.delay.length; ++i) { delayNode.delayTime.linearRampToValueAtTime (val.delay[i].val, val.delay[i].time + audio_ctx.currentTime ); } } if (!val.feedback.length) feedbackGainNode.gain.value = val.feedback.val; else { for (var i = 0; i < val.feedback.length; ++i) { feedbackGainNode.gain.linearRampToValueAtTime (val.feedback[i].val, val.feedback[i].time + audio_ctx.currentTime ); } } if (!val.mix.length) { dryGainNode.gain.value = 1 - ((val.mix.val - 0.5) * 2); wetGainNode.gain.value = 1 - ((0.5 - val.mix.val) * 2); } else { for (var i = 0; i < val.mix.length; ++i) { dryGainNode.gain.linearRampToValueAtTime (1 - ((val.mix[i].val - 0.5) * 2), val.mix[i].time + audio_ctx.currentTime ); wetGainNode.gain.linearRampToValueAtTime (1 - ((0.5 - val.mix[i].val) * 2), val.mix[i].time + audio_ctx.currentTime ); } } return (filter_chain); }, update : function ( filter_chain, audio_ctx, val ) { // update filter chain... var inputNode = filter_chain[0]; var outputNode = filter_chain[1]; var dryGainNode = filter_chain[2]; var wetGainNode = filter_chain[3]; var feedbackGainNode = filter_chain[4]; var delayNode = filter_chain[5]; if (!val.delay.length) delayNode.delayTime.value = val.delay.val; else { for (var i = 0; i < val.delay.length; ++i) { delayNode.delayTime.linearRampToValueAtTime (val.delay[i].val, val.delay[i].time + audio_ctx.currentTime ); } } if (!val.feedback.length) feedbackGainNode.gain.value = val.feedback.val; else { for (var i = 0; i < val.feedback.length; ++i) { feedbackGainNode.gain.linearRampToValueAtTime (val.feedback[i].val, val.feedback[i].time + audio_ctx.currentTime ); } } if (!val.mix.length) { dryGainNode.gain.value = 1 - ((val.mix.val - 0.5) * 2); wetGainNode.gain.value = 1 - ((0.5 - val.mix.val) * 2); } else { for (var i = 0; i < val.mix.length; ++i) { dryGainNode.gain.linearRampToValueAtTime (1 - ((val.mix[i].val - 0.5) * 2), val.mix[i].time + audio_ctx.currentTime ); wetGainNode.gain.linearRampToValueAtTime (1 - ((0.5 - val.mix[i].val) * 2), val.mix[i].time + audio_ctx.currentTime ); } } // --- } }; }, Distortion : function ( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { var wave_shaper = audio_ctx.createWaveShaper (); // var gain = parseInt (0.5 * 100, 10); var compute_dist = function ( val ) { var gain = parseInt ( (val / 1) * 100, 10); var n_samples = 44100; var curve = new Float32Array (n_samples); var deg = Math.PI / 180; var x; for (var i = 0; i < n_samples; ++i ) { x = i * 2 / n_samples - 1; curve[i] = (3 + gain) * x * 20 * deg / (Math.PI + gain * Math.abs(x)); } return (curve); }; for (var k = 0; k < val.length; ++k) { var curr = val[k]; if (curr.length) { for (var i = 0; i < curr.length; ++i) { wave_shaper.curve.linearRampToValueAtTime (compute_dist(curr[i].val), audio_ctx.currentTime + curr[i].time); } } else { wave_shaper.curve = compute_dist (curr.val); } } source.connect (wave_shaper); wave_shaper.connect (destination); return (wave_shaper); }, update : function ( filter, audio_ctx, val ) { var compute_dist = function ( val ) { var gain = parseInt ( (val / 1) * 100, 10); var n_samples = 44100; var curve = new Float32Array (n_samples); var deg = Math.PI / 180; var x; for (var i = 0; i < n_samples; ++i ) { x = i * 2 / n_samples - 1; curve[i] = (3 + gain) * x * 20 * deg / (Math.PI + gain * Math.abs(x)); } return (curve); }; for (var k = 0; k < val.length; ++k) { var curr = val[k]; if (curr.length) { for (var i = 0; i < curr.length; ++i) { filter.curve.linearRampToValueAtTime (compute_dist(curr[i].val), audio_ctx.currentTime + curr[i].time); } } else { filter.curve = compute_dist (curr.val); } } // ---- } }; }, Reverb : function ( val ) { return { filter : function ( audio_ctx, destination, source, duration ) { // ---- var inputNode = audio_ctx.createGain(); var reverbNode = audio_ctx.createConvolver(); var outputNode = audio_ctx.createGain(); var wetGainNode = audio_ctx.createGain(); var dryGainNode = audio_ctx.createGain(); source.connect (inputNode); inputNode.connect (reverbNode); reverbNode.connect (wetGainNode); inputNode.connect (dryGainNode); dryGainNode.connect (outputNode); wetGainNode.connect (outputNode); outputNode.connect (destination); var filter_chain = [ inputNode, outputNode, reverbNode, dryGainNode, wetGainNode ]; // set defaults dryGainNode.gain.value = 1 - ((val.mix - 0.5) * 2); wetGainNode.gain.value = 1 - ((0.5 - val.mix) * 2); var length = audio_ctx.sampleRate * val.time; var impulse = audio_ctx.createBuffer (2, length, audio_ctx.sampleRate); var impulseL = impulse.getChannelData(0); var impulseR = impulse.getChannelData(1); var n, i; for (i = 0; i < length; i++) { n = val.reverse ? length - i : i; impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, val.decay); impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, val.decay); } reverbNode.buffer = impulse; return (filter_chain); }, update : function ( filter_chain, audio_ctx, val ) { audio_ctx = wavesurfer.backend.ac; var reverbNode = filter_chain[2]; var dryGainNode = filter_chain[3]; var wetGainNode = filter_chain[4]; dryGainNode.gain.value = 1 - ((val.mix - 0.5) * 2); wetGainNode.gain.value = 1 - ((0.5 - val.mix) * 2); var length = audio_ctx.sampleRate * val.time; var impulse = audio_ctx.createBuffer (2, length, audio_ctx.sampleRate); var impulseL = impulse.getChannelData(0); var impulseR = impulse.getChannelData(1); var n, i; for (i = 0; i < length; i++) { n = val.reverse ? length - i : i; impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, val.decay); impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, val.decay); } reverbNode.buffer = impulse; } }; } }; this.FXPreviewUpdate = updatePreview; this.FXPreviewStop = stopPreview; this.FXPreviewToggle = togglePreview; this.FXPreviewInit = initPreview; this.FXPreview = previewEffect; this.FX = applyEffect; this.FXBank = FXBank; this.Trim = TrimBuffer; this.Copy = CopyBufferSegment; this.Insert = InsertSegmentToBuffer; this.InsertFloatArrays = InsertFloatArrays; this.ReplaceFloatArrays = ReplaceFloatArrays; this.Replace = OverwriteBufferWithSegment; this.FullReplace = OverwriteBuffer; this.MakeSilence = MakeSilenceBuffer; this.DownloadFile = DownloadFile; this.DownloadFileCancel = DownloadFileCancel; // this.ComputeTopFrequencies = findTopFrequencies; // this.MatchTopFrequencies= killdTopFrequencies; // --- }; PKAE._deps.audioutils = AudioUtils; })( PKAudioEditor );