1788 lines
49 KiB
JavaScript
Executable File
1788 lines
49 KiB
JavaScript
Executable File
(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 ); |