/********************************************************************** Audacity: A Digital Audio Editor InterpolateAudio.cpp Dominic Mazzoni **********************************************************************/ #include <math.h> #include <stdlib.h> #include <wx/defs.h> #include "InterpolateAudio.h" #include "Matrix.h" #include "SampleFormat.h" static inline int imin(int x, int y) { return x<y? x: y; } static inline int imax(int x, int y) { return x>y? x: y; } // This function is a really dumb, simple way to interpolate audio, // if the more general InterpolateAudio function below doesn't have // enough data to work with. If the bad samples are in the middle, // it's literally linear. If it's on either edge, we add some decay // back to zero. static void LinearInterpolateAudio(float *buffer, int len, int firstBad, int numBad) { int i; float decay = 0.9f; if (firstBad==0) { float delta = buffer[numBad] - buffer[numBad+1]; float value = buffer[numBad]; i = numBad - 1; while (i >= 0) { value += delta; buffer[i] = value; value *= decay; delta *= decay; i--; } } else if (firstBad + numBad == len) { float delta = buffer[firstBad-1] - buffer[firstBad-2]; float value = buffer[firstBad-1]; i = firstBad; while (i < firstBad + numBad) { value += delta; buffer[i] = value; value *= decay; delta *= decay; i++; } } else { float v1 = buffer[firstBad-1]; float v2 = buffer[firstBad+numBad]; float value = v1; float delta = (v2 - v1) / (numBad+1); i = firstBad; while (i < firstBad + numBad) { value += delta; buffer[i] = value; i++; } } } // Here's the main interpolate function, using // Least Squares AutoRegression (LSAR): void InterpolateAudio(float *buffer, const size_t len, size_t firstBad, size_t numBad) { const auto N = len; wxASSERT(len > 0 && firstBad >= 0 && numBad < len && firstBad+numBad <= len); if(numBad >= len) return; //should never have been called! if (firstBad == 0) { // The algorithm below has a weird asymmetry in that it // performs poorly when interpolating to the left. If // we're asked to interpolate the left side of a buffer, // we just reverse the problem and try it that way. Floats buffer2{ len }; for(size_t i=0; i<len; i++) buffer2[len-1-i] = buffer[i]; InterpolateAudio(buffer2.get(), len, len-numBad, numBad); for(size_t i=0; i<len; i++) buffer[len-1-i] = buffer2[i]; return; } Vector s(len, buffer); // Choose P, the order of the autoregression equation const int IP = imin(imin(numBad * 3, 50), imax(firstBad - 1, len - (firstBad + numBad) - 1)); if (IP < 3 || IP >= N) { LinearInterpolateAudio(buffer, len, firstBad, numBad); return; } size_t P(IP); // Add a tiny amount of random noise to the input signal - // this sounds like a bad idea, but the amount we're adding // is only about 1 bit in 16-bit audio, and it's an extremely // effective way to avoid nearly-singular matrices. If users // run it more than once they get slightly different results; // this is sometimes even advantageous. for(size_t i=0; i<N; i++) s[i] += (rand()-(RAND_MAX/2))/(RAND_MAX*10000.0); // Solve for the best autoregression coefficients // using a least-squares fit to all of the non-bad // data we have in the buffer Matrix X(P, P); Vector b(P); for(size_t i = 0; i + P < len; i++) if (i+P < firstBad || i >= (firstBad + numBad)) for(size_t row=0; row<P; row++) { for(size_t col=0; col<P; col++) X[row][col] += (s[i+row] * s[i+col]); b[row] += s[i+P] * s[i+row]; } Matrix Xinv(P, P); if (!InvertMatrix(X, Xinv)) { // The matrix is singular! Fall back on linear... // In practice I have never seen this happen if // we add the tiny bit of random noise. LinearInterpolateAudio(buffer, len, firstBad, numBad); return; } // This vector now contains the autoregression coefficients const Vector &a = Xinv * b; // Create a matrix (a "Toeplitz" matrix, as it turns out) // which encodes the autoregressive relationship between // elements of the sequence. Matrix A(N-P, N); for(size_t row=0; row<N-P; row++) { for(size_t col=0; col<P; col++) A[row][row+col] = -a[col]; A[row][row+P] = 1; } // Split both the Toeplitz matrix and the signal into // two pieces. Note that this code could be made to // work even in the case where the "bad" samples are // not contiguous, but currently it assumes they are. // "u" is for unknown (bad) // "k" is for known (good) Matrix Au = MatrixSubset(A, 0, N-P, firstBad, numBad); Matrix A_left = MatrixSubset(A, 0, N-P, 0, firstBad); Matrix A_right = MatrixSubset(A, 0, N-P, firstBad+numBad, N-(firstBad+numBad)); Matrix Ak = MatrixConcatenateCols(A_left, A_right); const Vector &s_left = VectorSubset(s, 0, firstBad); const Vector &s_right = VectorSubset(s, firstBad+numBad, N-(firstBad+numBad)); const Vector &sk = VectorConcatenate(s_left, s_right); // Do some linear algebra to find the best possible // values that fill in the "bad" area Matrix AuT = TransposeMatrix(Au); Matrix X1 = MatrixMultiply(AuT, Au); Matrix X2(X1.Rows(), X1.Cols()); if (!InvertMatrix(X1, X2)) { // The matrix is singular! Fall back on linear... LinearInterpolateAudio(buffer, len, firstBad, numBad); return; } Matrix X2b = X2 * -1.0; Matrix X3 = MatrixMultiply(X2b, AuT); Matrix X4 = MatrixMultiply(X3, Ak); // This vector contains our best guess as to the // unknown values const Vector &su = X4 * sk; // Put the results into the return buffer for(size_t i=0; i<numBad; i++) buffer[firstBad+i] = (float)su[i]; }