Path: blob/main/misc/emulator/gba/user_scripts/XAudioJS/resampler.js
28798 views
//JavaScript Audio Resampler (c) 2011 - Grant Galitz1function Resampler(fromSampleRate, toSampleRate, channels, outputBufferSize, noReturn) {2this.fromSampleRate = fromSampleRate;3this.toSampleRate = toSampleRate;4this.channels = channels | 0;5this.outputBufferSize = outputBufferSize;6this.noReturn = !!noReturn;7this.initialize();8}9Resampler.prototype.initialize = function () {10//Perform some checks:11if (this.fromSampleRate > 0 && this.toSampleRate > 0 && this.channels > 0) {12if (this.fromSampleRate == this.toSampleRate) {13//Setup a resampler bypass:14this.resampler = this.bypassResampler; //Resampler just returns what was passed through.15this.ratioWeight = 1;16}17else {18this.ratioWeight = this.fromSampleRate / this.toSampleRate;19if (this.fromSampleRate < this.toSampleRate) {20/*21Use generic linear interpolation if upsampling,22as linear interpolation produces a gradient that we want23and works fine with two input sample points per output in this case.24*/25this.compileLinearInterpolationFunction();26this.lastWeight = 1;27}28else {29/*30Custom resampler I wrote that doesn't skip samples31like standard linear interpolation in high downsampling.32This is more accurate than linear interpolation on downsampling.33*/34this.compileMultiTapFunction();35this.tailExists = false;36this.lastWeight = 0;37}38this.initializeBuffers();39}40}41else {42throw(new Error("Invalid settings specified for the resampler."));43}44}45Resampler.prototype.compileLinearInterpolationFunction = function () {46var toCompile = "var bufferLength = buffer.length;\47var outLength = this.outputBufferSize;\48if ((bufferLength % " + this.channels + ") == 0) {\49if (bufferLength > 0) {\50var weight = this.lastWeight;\51var firstWeight = 0;\52var secondWeight = 0;\53var sourceOffset = 0;\54var outputOffset = 0;\55var outputBuffer = this.outputBuffer;\56for (; weight < 1; weight += " + this.ratioWeight + ") {\57secondWeight = weight % 1;\58firstWeight = 1 - secondWeight;";59for (var channel = 0; channel < this.channels; ++channel) {60toCompile += "outputBuffer[outputOffset++] = (this.lastOutput[" + channel + "] * firstWeight) + (buffer[" + channel + "] * secondWeight);";61}62toCompile += "}\63weight -= 1;\64for (bufferLength -= " + this.channels + ", sourceOffset = Math.floor(weight) * " + this.channels + "; outputOffset < outLength && sourceOffset < bufferLength;) {\65secondWeight = weight % 1;\66firstWeight = 1 - secondWeight;";67for (var channel = 0; channel < this.channels; ++channel) {68toCompile += "outputBuffer[outputOffset++] = (buffer[sourceOffset" + ((channel > 0) ? (" + " + channel) : "") + "] * firstWeight) + (buffer[sourceOffset + " + (this.channels + channel) + "] * secondWeight);";69}70toCompile += "weight += " + this.ratioWeight + ";\71sourceOffset = Math.floor(weight) * " + this.channels + ";\72}";73for (var channel = 0; channel < this.channels; ++channel) {74toCompile += "this.lastOutput[" + channel + "] = buffer[sourceOffset++];";75}76toCompile += "this.lastWeight = weight % 1;\77return this.bufferSlice(outputOffset);\78}\79else {\80return (this.noReturn) ? 0 : [];\81}\82}\83else {\84throw(new Error(\"Buffer was of incorrect sample length.\"));\85}";86this.resampler = Function("buffer", toCompile);87}88Resampler.prototype.compileMultiTapFunction = function () {89var toCompile = "var bufferLength = buffer.length;\90var outLength = this.outputBufferSize;\91if ((bufferLength % " + this.channels + ") == 0) {\92if (bufferLength > 0) {\93var weight = 0;";94for (var channel = 0; channel < this.channels; ++channel) {95toCompile += "var output" + channel + " = 0;"96}97toCompile += "var actualPosition = 0;\98var amountToNext = 0;\99var alreadyProcessedTail = !this.tailExists;\100this.tailExists = false;\101var outputBuffer = this.outputBuffer;\102var outputOffset = 0;\103var currentPosition = 0;\104do {\105if (alreadyProcessedTail) {\106weight = " + this.ratioWeight + ";";107for (channel = 0; channel < this.channels; ++channel) {108toCompile += "output" + channel + " = 0;"109}110toCompile += "}\111else {\112weight = this.lastWeight;";113for (channel = 0; channel < this.channels; ++channel) {114toCompile += "output" + channel + " = this.lastOutput[" + channel + "];"115}116toCompile += "alreadyProcessedTail = true;\117}\118while (weight > 0 && actualPosition < bufferLength) {\119amountToNext = 1 + actualPosition - currentPosition;\120if (weight >= amountToNext) {";121for (channel = 0; channel < this.channels; ++channel) {122toCompile += "output" + channel + " += buffer[actualPosition++] * amountToNext;"123}124toCompile += "currentPosition = actualPosition;\125weight -= amountToNext;\126}\127else {";128for (channel = 0; channel < this.channels; ++channel) {129toCompile += "output" + channel + " += buffer[actualPosition" + ((channel > 0) ? (" + " + channel) : "") + "] * weight;"130}131toCompile += "currentPosition += weight;\132weight = 0;\133break;\134}\135}\136if (weight <= 0) {";137for (channel = 0; channel < this.channels; ++channel) {138toCompile += "outputBuffer[outputOffset++] = output" + channel + " / " + this.ratioWeight + ";"139}140toCompile += "}\141else {\142this.lastWeight = weight;";143for (channel = 0; channel < this.channels; ++channel) {144toCompile += "this.lastOutput[" + channel + "] = output" + channel + ";"145}146toCompile += "this.tailExists = true;\147break;\148}\149} while (actualPosition < bufferLength && outputOffset < outLength);\150return this.bufferSlice(outputOffset);\151}\152else {\153return (this.noReturn) ? 0 : [];\154}\155}\156else {\157throw(new Error(\"Buffer was of incorrect sample length.\"));\158}";159this.resampler = Function("buffer", toCompile);160}161Resampler.prototype.bypassResampler = function (buffer) {162if (this.noReturn) {163//Set the buffer passed as our own, as we don't need to resample it:164this.outputBuffer = buffer;165return buffer.length;166}167else {168//Just return the buffer passsed:169return buffer;170}171}172Resampler.prototype.bufferSlice = function (sliceAmount) {173if (this.noReturn) {174//If we're going to access the properties directly from this object:175return sliceAmount;176}177else {178//Typed array and normal array buffer section referencing:179try {180return this.outputBuffer.subarray(0, sliceAmount);181}182catch (error) {183try {184//Regular array pass:185this.outputBuffer.length = sliceAmount;186return this.outputBuffer;187}188catch (error) {189//Nightly Firefox 4 used to have the subarray function named as slice:190return this.outputBuffer.slice(0, sliceAmount);191}192}193}194}195Resampler.prototype.initializeBuffers = function () {196//Initialize the internal buffer:197try {198this.outputBuffer = new Float32Array(this.outputBufferSize);199this.lastOutput = new Float32Array(this.channels);200}201catch (error) {202this.outputBuffer = [];203this.lastOutput = [];204}205}206207