/*
录音 Recorder扩展，音频变速变调转换，本代码从Sonic.java移植
https://github.com/xiangyuecn/Recorder

Recorder.Sonic(set)
Recorder.Sonic.Async(set)
	有两种构造方法：Sonic是同步方法，Sonic.Async是异步方法，同步方法简单直接但处理量大时会消耗大量时间，主要用于一次性的处理；异步方法由WebWorker在后台进行运算处理，但异步方法不一定能成功开启（低版本浏览器），主要用于实时处理。
	
	注意：异步方法调用后必须调用flush方法，否则会产生内存泄露。
	
【构造初始化参数】
	set:{
		sampleRate:待处理pcm的采样率，就是input输入的buffer的采样率
	}

【功能配置调用函数】同步异步通用，以下num取值正常为0.1-2.0，超过这个范围也是可以的，但不推荐
	.setPitch(num) num:0.1-n，变调不变速（会说话的汤姆猫），男女变声，只调整音调，不改变播放速度，默认为1.0不调整
	.setSpeed(num) num:0.1-n，变速不变调（快放慢放），只调整播放速度，不改变音调，默认为1.0不调整
	.setRate(num) num:0.1-n，变速变调，越小越缓重，越大越尖锐，会改变播放速度和音调，默认为1.0不调整
	.setVolume(num) num:0.1-n，调整音量，默认为1.0不调整
	.setChordPitch(bool) bool:默认false，作用未知，不推荐使用
	.setQuality(num) num:0或1，默认0时会减小输入采样率来提供处理速度，变调时才会用到，不推荐使用

【同步调用方法】
	.input(buffer) buffer:[Int16,...] 一维数组，输入pcm数据，返回转换后的部分pcm数据，完整输出需要调用flush；返回值[Int16,...]长度可能为0，代表没有数据被转换；此方法是耗时的方法，一次性处理大量pcm需要切片+setTimeout优化
	.flush() 将残余的未转换的pcm数据完成转换并返回；返回值[Int16,...]长度可能为0，代表没有数据被转换，flush后不能再调用input
【异步调用方法】
	.input(buffer,callback) callback:fn(pcm)，和同步方法相同，只是返回值通过callback返回
	.flush(callback) callback:fn(pcm)，和同步方法相同，只是返回值通过callback返回
*/
(function(factory){
	var browser=typeof window=="object" && !!window.document;
	var win=browser?window:Object; //非浏览器环境，Recorder挂载在Object下面
	var rec=win.Recorder,ni=rec.i18n;
	factory(rec,ni,ni.$T,browser);
}(function(Recorder,i18n,$T,isBrowser){
"use strict";

//是否支持web worker
var HasWebWorker=isBrowser && typeof Worker=="function";

function SonicFunction(SonicFunction_set){//用函数包裹方便Web Worker化

//暴露接口
var fn=function(set){
	this.set=set;
	
	var sonic=Sonic_Class(this);
	this.sonic=sonic;
	sonic.New(set.sampleRate,1);
};
fn.prototype=SonicFunction.prototype={
	input:function(buffer){
		this.sonic.writeShortToStream(buffer);
		return this.sonic.readShortFromStream();
	}
	,flush:function(){
		this.sonic.flushStream();
		return this.sonic.readShortFromStream();
	}
};



//java 兼容环境
var System={
	arraycopy:function(src,srcPos,dest,destPos,len){
		for(var i=0;i<len;i++){
			dest[destPos+i]=src[srcPos+i];
		};
	}
};


/* Sonic library
   Copyright 2010, 2011
   Bill Cox
   This file is part of the Sonic Library.

   This file is licensed under the Apache 2.0 license.
   
   https://github.com/waywardgeek/sonic/blob/71c51195de71627d7443d05378c680ba756545e8/Sonic.java
*/
//Sonic.java 转写 js
function Sonic_Class(FnObj) {

    var SONIC_MIN_PITCH = 65;
    var SONIC_MAX_PITCH = 400;
    // This is used to down-sample some inputs to improve speed
    var SONIC_AMDF_FREQ = 4000;
    // The number of points to use in the sinc FIR filter for resampling.
    var SINC_FILTER_POINTS = 12;
    var SINC_TABLE_SIZE = 601;

    // Lookup table for windowed sinc function of SINC_FILTER_POINTS points.
    var sincTable = [
        0, 0, 0, 0, 0, 0, 0, -1, -1, -2, -2, -3, -4, -6, -7, -9, -10, -12, -14,
        -17, -19, -21, -24, -26, -29, -32, -34, -37, -40, -42, -44, -47, -48, -50,
        -51, -52, -53, -53, -53, -52, -50, -48, -46, -43, -39, -34, -29, -22, -16,
        -8, 0, 9, 19, 29, 41, 53, 65, 79, 92, 107, 121, 137, 152, 168, 184, 200,
        215, 231, 247, 262, 276, 291, 304, 317, 328, 339, 348, 357, 363, 369, 372,
        374, 375, 373, 369, 363, 355, 345, 332, 318, 300, 281, 259, 234, 208, 178,
        147, 113, 77, 39, 0, -41, -85, -130, -177, -225, -274, -324, -375, -426,
        -478, -530, -581, -632, -682, -731, -779, -825, -870, -912, -951, -989,
        -1023, -1053, -1080, -1104, -1123, -1138, -1149, -1154, -1155, -1151,
        -1141, -1125, -1105, -1078, -1046, -1007, -963, -913, -857, -796, -728,
        -655, -576, -492, -403, -309, -210, -107, 0, 111, 225, 342, 462, 584, 708,
        833, 958, 1084, 1209, 1333, 1455, 1575, 1693, 1807, 1916, 2022, 2122, 2216,
        2304, 2384, 2457, 2522, 2579, 2625, 2663, 2689, 2706, 2711, 2705, 2687,
        2657, 2614, 2559, 2491, 2411, 2317, 2211, 2092, 1960, 1815, 1658, 1489,
        1308, 1115, 912, 698, 474, 241, 0, -249, -506, -769, -1037, -1310, -1586,
        -1864, -2144, -2424, -2703, -2980, -3254, -3523, -3787, -4043, -4291,
        -4529, -4757, -4972, -5174, -5360, -5531, -5685, -5819, -5935, -6029,
        -6101, -6150, -6175, -6175, -6149, -6096, -6015, -5905, -5767, -5599,
        -5401, -5172, -4912, -4621, -4298, -3944, -3558, -3141, -2693, -2214,
        -1705, -1166, -597, 0, 625, 1277, 1955, 2658, 3386, 4135, 4906, 5697, 6506,
        7332, 8173, 9027, 9893, 10769, 11654, 12544, 13439, 14335, 15232, 16128,
        17019, 17904, 18782, 19649, 20504, 21345, 22170, 22977, 23763, 24527,
        25268, 25982, 26669, 27327, 27953, 28547, 29107, 29632, 30119, 30569,
        30979, 31349, 31678, 31964, 32208, 32408, 32565, 32677, 32744, 32767,
        32744, 32677, 32565, 32408, 32208, 31964, 31678, 31349, 30979, 30569,
        30119, 29632, 29107, 28547, 27953, 27327, 26669, 25982, 25268, 24527,
        23763, 22977, 22170, 21345, 20504, 19649, 18782, 17904, 17019, 16128,
        15232, 14335, 13439, 12544, 11654, 10769, 9893, 9027, 8173, 7332, 6506,
        5697, 4906, 4135, 3386, 2658, 1955, 1277, 625, 0, -597, -1166, -1705,
        -2214, -2693, -3141, -3558, -3944, -4298, -4621, -4912, -5172, -5401,
        -5599, -5767, -5905, -6015, -6096, -6149, -6175, -6175, -6150, -6101,
        -6029, -5935, -5819, -5685, -5531, -5360, -5174, -4972, -4757, -4529,
        -4291, -4043, -3787, -3523, -3254, -2980, -2703, -2424, -2144, -1864,
        -1586, -1310, -1037, -769, -506, -249, 0, 241, 474, 698, 912, 1115, 1308,
        1489, 1658, 1815, 1960, 2092, 2211, 2317, 2411, 2491, 2559, 2614, 2657,
        2687, 2705, 2711, 2706, 2689, 2663, 2625, 2579, 2522, 2457, 2384, 2304,
        2216, 2122, 2022, 1916, 1807, 1693, 1575, 1455, 1333, 1209, 1084, 958, 833,
        708, 584, 462, 342, 225, 111, 0, -107, -210, -309, -403, -492, -576, -655,
        -728, -796, -857, -913, -963, -1007, -1046, -1078, -1105, -1125, -1141,
        -1151, -1155, -1154, -1149, -1138, -1123, -1104, -1080, -1053, -1023, -989,
        -951, -912, -870, -825, -779, -731, -682, -632, -581, -530, -478, -426,
        -375, -324, -274, -225, -177, -130, -85, -41, 0, 39, 77, 113, 147, 178,
        208, 234, 259, 281, 300, 318, 332, 345, 355, 363, 369, 373, 375, 374, 372,
        369, 363, 357, 348, 339, 328, 317, 304, 291, 276, 262, 247, 231, 215, 200,
        184, 168, 152, 137, 121, 107, 92, 79, 65, 53, 41, 29, 19, 9, 0, -8, -16,
        -22, -29, -34, -39, -43, -46, -48, -50, -52, -53, -53, -53, -52, -51, -50,
        -48, -47, -44, -42, -40, -37, -34, -32, -29, -26, -24, -21, -19, -17, -14,
        -12, -10, -9, -7, -6, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0
    ];

    var inputBuffer;
    var outputBuffer;
    var pitchBuffer;
    var downSampleBuffer;
    var speed=0;
    var volume=0;
    var pitch=0;
    var rate=0;
    var oldRatePosition=0;
    var newRatePosition=0;
    var useChordPitch=false;
    var quality=0;
    var numChannels=0;
    var inputBufferSize=0;
    var pitchBufferSize=0;
    var outputBufferSize=0;
    var numInputSamples=0;
    var numOutputSamples=0;
    var numPitchSamples=0;
    var minPeriod=0;
    var maxPeriod=0;
    var maxRequired=0;
    var remainingInputToCopy=0;
    var sampleRate=0;
    var prevPeriod=0;
    var prevMinDiff=0;
    var minDiff=0;
    var maxDiff=0;

    // Resize the array.
    function resize(
        oldArray,
        newLength)
    {
        newLength *= numChannels;
        var newArray = new Int16Array(newLength);
        var length = oldArray.length <= newLength? oldArray.length : newLength;

        System.arraycopy(oldArray, 0, newArray, 0, length);
        return newArray;
    }

    // Move samples from one array to another.  May move samples down within an array, but not up.
    function move(
        dest,
        destPos,
        source,
        sourcePos,
        numSamples)
    {
        System.arraycopy(source, sourcePos*numChannels, dest, destPos*numChannels, numSamples*numChannels);
    }

    // Scale the samples by the factor.
    function scaleSamples(
        samples,
        position,
        numSamples,
        volume)
    {
        var fixedPointVolume = Math.floor(volume*4096.0);
        var start = position*numChannels;
        var stop = start + numSamples*numChannels;

        for(var xSample = start; xSample < stop; xSample++) {
            var value = (samples[xSample]*fixedPointVolume) >> 12;
            if(value > 32767) {
                value = 32767;
            } else if(value < -32767) {
                value = -32767;
            }
            samples[xSample] = value;
        }
    }

    // Get the speed of the stream.
    function getSpeed()
    {
        return speed;
    }

    // Set the speed of the stream.
    function setSpeed(
        speed_)
    {
        speed = speed_;
    }

    // Get the pitch of the stream.
    function getPitch()
    {
        return pitch;
    }

    // Set the pitch of the stream.
    function setPitch(
        pitch_)
    {
        pitch = pitch_;
    }

    // Get the rate of the stream.
    function getRate()
    {
        return rate;
    }

    // Set the playback rate of the stream. This scales pitch and speed at the same time.
    function setRate(
        rate_)
    {
		if(rate!=rate_){//允许任意设置
			rate = rate_;
			oldRatePosition = 0;
			newRatePosition = 0;
		}
    }

    // Get the vocal chord pitch setting.
    function getChordPitch()
    {
        return useChordPitch;
    }

    // Set the vocal chord mode for pitch computation.  Default is off.
    function setChordPitch(
        useChordPitch_)
    {
        useChordPitch = useChordPitch_;
    }

    // Get the quality setting.
    function getQuality()
    {
        return quality;
    }

    // Set the "quality".  Default 0 is virtually as good as 1, but very much faster.
    function setQuality(
        quality_)
    {
        quality = quality_;
    }

    // Get the scaling factor of the stream.
    function getVolume()
    {
        return volume;
    }

    // Set the scaling factor of the stream.
    function setVolume(
        volume_)
    {
        volume = volume_;
    }

    // Allocate stream buffers.
    function allocateStreamBuffers(
        sampleRate_,
        numChannels_)
    {
        minPeriod = Math.floor(sampleRate_/SONIC_MAX_PITCH);
        maxPeriod = Math.floor(sampleRate_/SONIC_MIN_PITCH);
        maxRequired = 2*maxPeriod;
        inputBufferSize = maxRequired;
        inputBuffer = new Int16Array(maxRequired*numChannels_);
        outputBufferSize = maxRequired;
        outputBuffer = new Int16Array(maxRequired*numChannels_);
        pitchBufferSize = maxRequired;
        pitchBuffer = new Int16Array(maxRequired*numChannels_);
        downSampleBuffer = new Int16Array(maxRequired);
        sampleRate = sampleRate_;
        numChannels = numChannels_;
        oldRatePosition = 0;
        newRatePosition = 0;
        prevPeriod = 0;
    }

    // Create a sonic stream.
    function Sonic(
        sampleRate,
        numChannels)
    {
        allocateStreamBuffers(sampleRate, numChannels);
        speed = 1.0;
        pitch = 1.0;
        volume = 1.0;
        rate = 1.0;
        oldRatePosition = 0;
        newRatePosition = 0;
        useChordPitch = false;
        quality = 0;
    }

    // Get the sample rate of the stream.
    function getSampleRate()
    {
        return sampleRate;
    }

    // Set the sample rate of the stream.  This will cause samples buffered in the stream to be lost.
    function setSampleRate(
        sampleRate)
    {
        allocateStreamBuffers(sampleRate, numChannels);
    }

    // Get the number of channels.
    function getNumChannels()
    {
        return numChannels;
    }

    // Set the num channels of the stream.  This will cause samples buffered in the stream to be lost.
    function setNumChannels(
        numChannels)
    {
        allocateStreamBuffers(sampleRate, numChannels);
    }

    // Enlarge the output buffer if needed.
    function enlargeOutputBufferIfNeeded(
        numSamples)
    {
        if(numOutputSamples + numSamples > outputBufferSize) {
            outputBufferSize += (outputBufferSize >> 1) + numSamples;
            outputBuffer = resize(outputBuffer, outputBufferSize);
        }
    }

    // Enlarge the input buffer if needed.
    function enlargeInputBufferIfNeeded(
        numSamples)
    {
        if(numInputSamples + numSamples > inputBufferSize) {
            inputBufferSize += (inputBufferSize >> 1) + numSamples;
            inputBuffer = resize(inputBuffer, inputBufferSize);
        }
    }

    // Add the input samples to the input buffer.
    function addShortSamplesToInputBuffer(
        samples,
        numSamples)
    {
        if(numSamples == 0) {
            return;
        }
        enlargeInputBufferIfNeeded(numSamples);
        move(inputBuffer, numInputSamples, samples, 0, numSamples);
        numInputSamples += numSamples;
    }

    // Remove input samples that we have already processed.
    function removeInputSamples(
        position)
    {
        var remainingSamples = numInputSamples - position;

        move(inputBuffer, 0, inputBuffer, position, remainingSamples);
        numInputSamples = remainingSamples;
    }

    // Just copy from the array to the output buffer
    function copyToOutput(
        samples,
        position,
        numSamples)
    {
        enlargeOutputBufferIfNeeded(numSamples);
        move(outputBuffer, numOutputSamples, samples, position, numSamples);
        numOutputSamples += numSamples;
    }

    // Just copy from the input buffer to the output buffer.  Return num samples copied.
    function copyInputToOutput(
        position)
    {
        var numSamples = remainingInputToCopy;

        if(numSamples > maxRequired) {
            numSamples = maxRequired;
        }
        copyToOutput(inputBuffer, position, numSamples);
        remainingInputToCopy -= numSamples;
        return numSamples;
    }


    // Read short data out of the stream.  Sometimes no data will be available, and zero
    // is returned, which is not an error condition.
    function readShortFromStream() //已改成直接返回所有的Int16Array
    {
        var numSamples = numOutputSamples;
		var samples=new Int16Array(numSamples);
        var remainingSamples = 0;

        if(numSamples == 0) {
            return samples;
        }
        move(samples, 0, outputBuffer, 0, numSamples);
        move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
        numOutputSamples = remainingSamples;
        return samples;
    }

    // Force the sonic stream to generate output using whatever data it currently
    // has.  No extra delay will be added to the output, but flushing in the middle of
    // words could introduce distortion.
    function flushStream()
    {
        var remainingSamples = numInputSamples;
        var s = speed/pitch;
        var r = rate*pitch;
        var expectedOutputSamples = Math.floor(numOutputSamples + Math.floor((remainingSamples/s + numPitchSamples)/r + 0.5));

        // Add enough silence to flush both input and pitch buffers.
        enlargeInputBufferIfNeeded(remainingSamples + 2*maxRequired);
        for(var xSample = 0; xSample < 2*maxRequired*numChannels; xSample++) {
            inputBuffer[remainingSamples*numChannels + xSample] = 0;
        }
        numInputSamples += 2*maxRequired;
        writeShortToStream(null, 0);
        // Throw away any extra samples we generated due to the silence we added.
        if(numOutputSamples > expectedOutputSamples) {
            numOutputSamples = expectedOutputSamples;
        }
        // Empty input and pitch buffers.
        numInputSamples = 0;
        remainingInputToCopy = 0;
        numPitchSamples = 0;
    }

    // Return the number of samples in the output buffer
    function samplesAvailable()
    {
        return numOutputSamples;
    }

    // If skip is greater than one, average skip samples together and write them to
    // the down-sample buffer.  If numChannels is greater than one, mix the channels
    // together as we down sample.
    function downSampleInput(
        samples,
        position,
        skip)
    {
        var numSamples = Math.floor(maxRequired/skip);
        var samplesPerValue = numChannels*skip;
        var value;

        position *= numChannels;
        for(var i = 0; i < numSamples; i++) {
            value = 0;
            for(var j = 0; j < samplesPerValue; j++) {
                value += samples[position + i*samplesPerValue + j];
            }
            value = Math.floor(value/samplesPerValue);
            downSampleBuffer[i] = value;
        }
    }

    // Find the best frequency match in the range, and given a sample skip multiple.
    // For now, just find the pitch of the first channel.
    function findPitchPeriodInRange(
        samples,
        position,
        minPeriod,
        maxPeriod)
    {
        var bestPeriod = 0, worstPeriod = 255;
        var minDiff_ = 1, maxDiff_ = 0;

        position *= numChannels;
        for(var period = minPeriod; period <= maxPeriod; period++) {
            var diff = 0;
            for(var i = 0; i < period; i++) {
                var sVal = samples[position + i];
                var pVal = samples[position + period + i];
                diff += sVal >= pVal? sVal - pVal : pVal - sVal;
            }
            /* Note that the highest number of samples we add into diff will be less
               than 256, since we skip samples.  Thus, diff is a 24 bit number, and
               we can safely multiply by numSamples without overflow */
            if(diff*bestPeriod < minDiff_*period) {
                minDiff_ = diff;
                bestPeriod = period;
            }
            if(diff*worstPeriod > maxDiff_*period) {
                maxDiff_ = diff;
                worstPeriod = period;
            }
        }
        minDiff = Math.floor(minDiff_/bestPeriod);
        maxDiff = Math.floor(maxDiff_/worstPeriod);

        return bestPeriod;
    }

    // At abrupt ends of voiced words, we can have pitch periods that are better
    // approximated by the previous pitch period estimate.  Try to detect this case.
    function prevPeriodBetter(
        minDiff,
        maxDiff,
        preferNewPeriod)
    {
        if(minDiff == 0 || prevPeriod == 0) {
            return false;
        }
        if(preferNewPeriod) {
            if(maxDiff > minDiff*3) {
                // Got a reasonable match this period
                return false;
            }
            if(minDiff*2 <= prevMinDiff*3) {
                // Mismatch is not that much greater this period
                return false;
            }
        } else {
            if(minDiff <= prevMinDiff) {
                return false;
            }
        }
        return true;
    }

    // Find the pitch period.  This is a critical step, and we may have to try
    // multiple ways to get a good answer.  This version uses AMDF.  To improve
    // speed, we down sample by an integer factor get in the 11KHz range, and then
    // do it again with a narrower frequency range without down sampling
    function findPitchPeriod(
        samples,
        position,
        preferNewPeriod)
    {
        var period, retPeriod;
        var skip = 1;

        if(sampleRate > SONIC_AMDF_FREQ && quality == 0) {
            skip = Math.floor(sampleRate/SONIC_AMDF_FREQ);
        }
        if(numChannels == 1 && skip == 1) {
            period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod);
        } else {
            downSampleInput(samples, position, skip);
            period = findPitchPeriodInRange(downSampleBuffer, 0, Math.floor(minPeriod/skip),
                Math.floor(maxPeriod/skip));
            if(skip != 1) {
                period *= skip;
                var minP = period - (skip << 2);
                var maxP = period + (skip << 2);
                if(minP < minPeriod) {
                    minP = minPeriod;
                }
                if(maxP > maxPeriod) {
                    maxP = maxPeriod;
                }
                if(numChannels == 1) {
                    period = findPitchPeriodInRange(samples, position, minP, maxP);
                } else {
                    downSampleInput(samples, position, 1);
                    period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP);
                }
            }
        }
        if(prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) {
            retPeriod = prevPeriod;
        } else {
            retPeriod = period;
        }
        prevMinDiff = minDiff;
        prevPeriod = period;
        return retPeriod;
    }

    // Overlap two sound segments, ramp the volume of one down, while ramping the
    // other one from zero up, and add them, storing the result at the output.
    function overlapAdd(
        numSamples,
        numChannels,
        out,
        outPos,
        rampDown,
        rampDownPos,
        rampUp,
        rampUpPos)
    {
         for(var i = 0; i < numChannels; i++) {
            var o = outPos*numChannels + i;
            var u = rampUpPos*numChannels + i;
            var d = rampDownPos*numChannels + i;
            for(var t = 0; t < numSamples; t++) {
                out[o] = Math.floor((rampDown[d]*(numSamples - t) + rampUp[u]*t)/numSamples);
                o += numChannels;
                d += numChannels;
                u += numChannels;
            }
        }
    }

    // Overlap two sound segments, ramp the volume of one down, while ramping the
    // other one from zero up, and add them, storing the result at the output.
    function overlapAddWithSeparation(
        numSamples,
        numChannels,
        separation,
        out,
        outPos,
        rampDown,
        rampDownPos,
        rampUp,
        rampUpPos)
    {
        for(var i = 0; i < numChannels; i++) {
            var o = outPos*numChannels + i;
            var u = rampUpPos*numChannels + i;
            var d = rampDownPos*numChannels + i;
            for(var t = 0; t < numSamples + separation; t++) {
                if(t < separation) {
                    out[o] = Math.floor(rampDown[d]*(numSamples - t)/numSamples);
                    d += numChannels;
                } else if(t < numSamples) {
                    out[o] = Math.floor((rampDown[d]*(numSamples - t) + rampUp[u]*(t - separation))/numSamples);
                    d += numChannels;
                    u += numChannels;
                } else {
                    out[o] = Math.floor(rampUp[u]*(t - separation)/numSamples);
                    u += numChannels;
                }
                o += numChannels;
            }
        }
    }

    // Just move the new samples in the output buffer to the pitch buffer
    function moveNewSamplesToPitchBuffer(
        originalNumOutputSamples)
    {
        var numSamples = numOutputSamples - originalNumOutputSamples;

        if(numPitchSamples + numSamples > pitchBufferSize) {
            pitchBufferSize += (pitchBufferSize >> 1) + numSamples;
            pitchBuffer = resize(pitchBuffer, pitchBufferSize);
        }
        move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples);
        numOutputSamples = originalNumOutputSamples;
        numPitchSamples += numSamples;
    }

    // Remove processed samples from the pitch buffer.
    function removePitchSamples(
        numSamples)
    {
        if(numSamples == 0) {
            return;
        }
        move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples);
        numPitchSamples -= numSamples;
    }

    // Change the pitch.  The latency this introduces could be reduced by looking at
    // past samples to determine pitch, rather than future.
    function adjustPitch(
        originalNumOutputSamples)
    {
        var period, newPeriod, separation;
        var position = 0;

        if(numOutputSamples == originalNumOutputSamples) {
            return;
        }
        moveNewSamplesToPitchBuffer(originalNumOutputSamples);
        while(numPitchSamples - position >= maxRequired) {
            period = findPitchPeriod(pitchBuffer, position, false);
            newPeriod = Math.floor(period/pitch);
            enlargeOutputBufferIfNeeded(newPeriod);
            if(pitch >= 1.0) {
                overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer,
                        position, pitchBuffer, position + period - newPeriod);
            } else {
                separation = newPeriod - period;
                overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples,
                        pitchBuffer, position, pitchBuffer, position);
            }
            numOutputSamples += newPeriod;
            position += period;
        }
        removePitchSamples(position);
    }

    // Aproximate the sinc function times a Hann window from the sinc table.
    function findSincCoefficient(i, ratio, width) {
        var lobePoints = Math.floor((SINC_TABLE_SIZE-1)/SINC_FILTER_POINTS);
        var left = Math.floor(i*lobePoints + (ratio*lobePoints)/width);
        var right = left + 1;
        var position = i*lobePoints*width + ratio*lobePoints - left*width;
        var leftVal = sincTable[left];
        var rightVal = sincTable[right];

        return Math.floor(((leftVal*(width - position) + rightVal*position) << 1)/width);
    }

    // Return 1 if value >= 0, else -1.  This represents the sign of value.
    function getSign(value) {
        return value >= 0? 1 : -1;
    }

    // Interpolate the new output sample.
    function interpolate(
        in_,
        inPos,  // Index to first sample which already includes channel offset.
        oldSampleRate,
        newSampleRate)
    {
        // Compute N-point sinc FIR-filter here.  Clip rather than overflow.
        var i;
        var total = 0;
        var position = newRatePosition*oldSampleRate;
        var leftPosition = oldRatePosition*newSampleRate;
        var rightPosition = (oldRatePosition + 1)*newSampleRate;
        var ratio = rightPosition - position - 1;
        var width = rightPosition - leftPosition;
        var weight, value;
        var oldSign;
        var overflowCount = 0;

        for (i = 0; i < SINC_FILTER_POINTS; i++) {
            weight = findSincCoefficient(i, ratio, width);
            /* printf("%u %f\n", i, weight); */
            value = in_[inPos + i*numChannels]*weight;
            oldSign = getSign(total);
            total += value;
            if (oldSign != getSign(total) && getSign(value) == oldSign) {
                /* We must have overflowed.  This can happen with a sinc filter. */
                overflowCount += oldSign;
            }
        }
        /* It is better to clip than to wrap if there was a overflow. */
        if (overflowCount > 0) {
            return 0x7FFF;
        } else if (overflowCount < 0) {
            return -0x8000;
        }
        return (total >> 16)&0xffff;
    }

    // Change the rate.
    function adjustRate(
        rate,
        originalNumOutputSamples)
    {
        var newSampleRate = Math.floor(sampleRate/rate);
        var oldSampleRate = sampleRate;
        var position;

        // Set these values to help with the integer math
        while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
            newSampleRate >>= 1;
            oldSampleRate >>= 1;
        }
        if(numOutputSamples == originalNumOutputSamples) {
            return;
        }
        moveNewSamplesToPitchBuffer(originalNumOutputSamples);
        // Leave at least one pitch sample in the buffer
        for(position = 0; position < numPitchSamples - 1; position++) {
            while((oldRatePosition + 1)*newSampleRate > newRatePosition*oldSampleRate) {
                enlargeOutputBufferIfNeeded(1);
                for(var i = 0; i < numChannels; i++) {
                    outputBuffer[numOutputSamples*numChannels + i] = interpolate(pitchBuffer,
                            position*numChannels + i, oldSampleRate, newSampleRate);
                }
                newRatePosition++;
                numOutputSamples++;
            }
            oldRatePosition++;
            if(oldRatePosition == oldSampleRate) {
                oldRatePosition = 0;
                if(newRatePosition != newSampleRate) {
                    throw new Error("Assertion failed: newRatePosition != newSampleRate\n");
                    //assert false;
                }
                newRatePosition = 0;
            }
        }
        removePitchSamples(position);
    }


    // Skip over a pitch period, and copy period/speed samples to the output
    function skipPitchPeriod(
        samples,
        position,
        speed,
        period)
    {
        var newSamples;

        if(speed >= 2.0) {
            newSamples = Math.floor(period/(speed - 1.0));
        } else {
            newSamples = period;
            remainingInputToCopy = Math.floor(period*(2.0 - speed)/(speed - 1.0));
        }
        enlargeOutputBufferIfNeeded(newSamples);
        overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position,
                samples, position + period);
        numOutputSamples += newSamples;
        return newSamples;
    }

    // Insert a pitch period, and determine how much input to copy directly.
    function insertPitchPeriod(
        samples,
        position,
        speed,
        period)
    {
        var newSamples;

        if(speed < 0.5) {
            newSamples = Math.floor(period*speed/(1.0 - speed));
        } else {
            newSamples = period;
            remainingInputToCopy = Math.floor(period*(2.0*speed - 1.0)/(1.0 - speed));
        }
        enlargeOutputBufferIfNeeded(period + newSamples);
        move(outputBuffer, numOutputSamples, samples, position, period);
        overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples,
                position + period, samples, position);
        numOutputSamples += period + newSamples;
        return newSamples;
    }

    // Resample as many pitch periods as we have buffered on the input.  Return 0 if
    // we fail to resize an input or output buffer.  Also scale the output by the volume.
    function changeSpeed(
        speed)
    {
        var numSamples = numInputSamples;
        var position = 0, period, newSamples;

        if(numInputSamples < maxRequired) {
            return;
        }
        do {
            if(remainingInputToCopy > 0) {
                newSamples = copyInputToOutput(position);
                position += newSamples;
            } else {
                period = findPitchPeriod(inputBuffer, position, true);
                if(speed > 1.0) {
                    newSamples = skipPitchPeriod(inputBuffer, position, speed, period);
                    position += period + newSamples;
                } else {
                    newSamples = insertPitchPeriod(inputBuffer, position, speed, period);
                    position += newSamples;
                }
            }
        } while(position + maxRequired <= numSamples);
        removeInputSamples(position);
    }

    // Resample as many pitch periods as we have buffered on the input.  Scale the output by the volume.
    function processStreamInput()
    {
        var originalNumOutputSamples = numOutputSamples;
        var s = speed/pitch;
        var r = rate;

        if(!useChordPitch) {
            r *= pitch;
        }
        if(s > 1.00001 || s < 0.99999) {
            changeSpeed(s);
        } else {
            copyToOutput(inputBuffer, 0, numInputSamples);
            numInputSamples = 0;
        }
        if(useChordPitch) {
            if(pitch != 1.0) {
                adjustPitch(originalNumOutputSamples);
            }
        } else if(r != 1.0) {
            adjustRate(r, originalNumOutputSamples);
        }
        if(volume != 1.0) {
            // Adjust output volume.
            scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples,
                volume);
        }
    }

    // Write the data to the input stream, and process it.
    function writeShortToStream(
        samples)
    {
        addShortSamplesToInputBuffer(samples, samples?samples.length:0);
        processStreamInput();
    }

	
	
	
	/**导出Sonic对象**/
	FnObj.setPitch=setPitch;
	FnObj.setRate=setRate;
	FnObj.setSpeed=setSpeed;
	FnObj.setVolume=setVolume;
	FnObj.setChordPitch=setChordPitch;
	FnObj.setQuality=setQuality;
	return {
		New:Sonic
		
		,flushStream:flushStream
		,writeShortToStream:writeShortToStream
		,readShortFromStream:readShortFromStream
	};
}

return new fn(SonicFunction_set);
};










Recorder.Sonic=SonicFunction;

//Worker异步化
var sonicWorker;
Recorder.BindDestroy("sonicWorker",function(){
	if(sonicWorker){
		Recorder.CLog("sonicWorker Destroy");
		sonicWorker&&sonicWorker.terminate();
		sonicWorker=null;
	};
});
//开启异步，如果返回null代表不支持，开启成功后必须调用flush方法，否则会内存泄露
var openList={id:0};
SonicFunction.Async=function(set){
	if(!HasWebWorker){
		Recorder.CLog($T("Ikdz::当前环境不支持Web Worker，不支持调用Sonic.Async"),3);
		return null;
	};
	var worker=sonicWorker;
	try{
		var onmsg=function(e){
			var ed=e.data;
			var cur=wk_ctxs[ed.id];
			if(ed.action=="init"){
				wk_ctxs[ed.id]={
					sampleRate:ed.sampleRate
					
					,sonicObj:wk_sonic({sampleRate:ed.sampleRate})
				};
			}else if(!cur){
				return;
			};
			
			switch(ed.action){
			case "flush":
				var pcm=cur.sonicObj.flush();
				self.postMessage({
					action:ed.action
					,id:ed.id
					,call:ed.call
					,pcm:pcm
				});
				
				cur.sonicObj=null;
				delete wk_ctxs[ed.id];
				break;
			case "input":
				var pcm=cur.sonicObj.input(ed.pcm);
				self.postMessage({
					action:ed.action
					,id:ed.id
					,call:ed.call
					,pcm:pcm
				});
				break;
			default:
				if(/^set/.test(ed.action)){
					cur.sonicObj[ed.action](ed.param);
				};
			};
		};
		if(!worker){
			//创建一个新Worker
			var jsCode=");var wk_ctxs={};self.onmessage="+onmsg;
			
			var sonicCode=Recorder.Sonic.toString();
			var url=(window.URL||webkitURL).createObjectURL(new Blob(["var wk_sonic=(",sonicCode,jsCode], {type:"text/javascript"}));
			
			worker=new Worker(url);
			setTimeout(function(){
				(window.URL||webkitURL).revokeObjectURL(url);//必须要释放，不然每次调用内存都明显泄露内存
			},10000);//chrome 83 file协议下如果直接释放，将会使WebWorker无法启动
			
			worker.onmessage=function(e){
				var ctx=openList[e.data.id];
				if(ctx){
					var fn=ctx.cbs[e.data.call];
					fn&&fn(e.data);
				};
			};
		};
		
		var ctx=new sonicWorkerCtx(worker,set);
		ctx.id=++openList.id;
		openList[ctx.id]=ctx;
		
		worker.postMessage({
			action:"init"
			,id:ctx.id
			,sampleRate:set.sampleRate
			
			,x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
		});
		
		
		sonicWorker=worker;
		return ctx;
	}catch(e){//出错了就不要提供了
		worker&&worker.terminate();
		
		console.error(e);
		return null;
	};
};

var sonicWorkerCtx=function(worker,set){
	this.worker=worker;
	this.set=set;
	
	this.cbs={i:0};
};
sonicWorkerCtx.prototype={
	cb:function(call){
		var This=this;
		var id="cb"+(++This.cbs.i);
		This.cbs[id]=function(data){
			delete This.cbs[id];
			call(data);
		};
		return id;
	}
	,flush:function(call){
		var This=this;if(!This.worker)return;
		
		This.worker.postMessage({
			action:"flush"
			,id:This.id
			,call:This.cb(function(data){
				call&&call(data.pcm);
				
				This.worker=null;
				delete openList[This.id];
				
				//疑似泄露检测 排除id
				var opens=-1;
				for(var k in openList){
					opens++;
				};
				if(opens){
					Recorder.CLog($T("IC5Y::sonic worker剩{1}个未flush",0,opens),3);
				};
			})
		});
	}
	,input:function(pcm,call){
		var This=this;if(!This.worker)return;
		
		This.worker.postMessage({
			action:"input"
			,id:This.id
			,pcm:pcm
			,call:This.cb(function(data){
				call&&call(data.pcm);
			})
		});
	}
};
var addWorkerCtxSet=function(key){
	sonicWorkerCtx.prototype[key]=function(param){
		var This=this;if(!This.worker)return;
		
		This.worker.postMessage({
			action:key
			,id:This.id
			,param:param
		});
	}
};
addWorkerCtxSet("setPitch");
addWorkerCtxSet("setRate");
addWorkerCtxSet("setSpeed");
addWorkerCtxSet("setVolume");
addWorkerCtxSet("setChordPitch");
addWorkerCtxSet("setQuality");

}));