An Audio Synthesis Textbook For Musicians, Digital Artists and Programmers by Mike Krzyzaniak

  1. //LinearInterpolation.c
  2. //gcc MKAiff.c LinearInterpolation.c -o LinearInterpolation
  3. #include "MKAiff.h"
  4. #include <math.h>
  5. #define TRANSPOSITION -21
  6. #define NUM_CHANNELS aiffNumChannels(prerecordedAudio)
  7. #define SAMPLE_RATE aiffSampleRate(prerecordedAudio)
  8. #define BITS_PER_SAMPLE aiffBitsPerSample(prerecordedAudio)
  9. #define PRERECORDED_AUDIO_PATH "demo.aif"
  10. #define BUFFER_NUM_FRAMES 4096
  11. #define BUFFER_NUM_SAMPLES (BUFFER_NUM_FRAMES * NUM_CHANNELS)
  12. void linearInterpolateBuffer(float* previousFrame, int numChannels, float* input, int inNumFrames, float* output, int outFrames);
  13. void cubicInterpolateBuffer(float* previous3Frames, int numChannels, float* input, int inNumFrames, float* output, int outNumFrames);
  14. int main()
  15. {
  16. MKAiff* prerecordedAudio = aiffWithContentsOfFile(PRERECORDED_AUDIO_PATH);
  17. if(prerecordedAudio == NULL) return 1;
  18. const float PLAYBACK_SPEED = pow(pow(2, -TRANSPOSITION), (1/12.0));
  19. const float NUM_SECONDS = (PLAYBACK_SPEED * aiffDurationInSeconds(prerecordedAudio));
  20. int i;
  21. MKAiff* aiff = aiffWithDurationInSeconds(NUM_CHANNELS, SAMPLE_RATE, BITS_PER_SAMPLE, NUM_SECONDS + 1);
  22. if(aiff == NULL) return 1;
  23. float audioBuffer[BUFFER_NUM_SAMPLES];
  24. float interpolatedAudioBuffer[(int)(BUFFER_NUM_SAMPLES * PLAYBACK_SPEED)];
  25. int numSamplesRead, numSamplesToWrite;
  26. //Linear Interpolation needs 1 previous frame
  27. float previousFrame[NUM_CHANNELS * 3];
  28. for(i=0; i<NUM_CHANNELS * 3; previousFrame[i++] = 0);
  29. /* //Cubic Interpolation needs 3 previous frames
  30. float previousFrame[NUM_CHANNELS * 3];
  31. for(i=0; i<NUM_CHANNELS * 3; previousFrame[i++] = 0);
  32. */
  33. do{
  34. numSamplesRead = aiffReadFloatingPointSamplesAtPlayhead(prerecordedAudio, audioBuffer, BUFFER_NUM_SAMPLES);
  35. numSamplesToWrite = numSamplesRead * PLAYBACK_SPEED;
  36. numSamplesToWrite -= (numSamplesToWrite % NUM_CHANNELS);
  37. linearInterpolateBuffer(previousFrame, NUM_CHANNELS, audioBuffer, numSamplesRead / NUM_CHANNELS, interpolatedAudioBuffer, numSamplesToWrite / NUM_CHANNELS);
  38. //cubicInterpolateBuffer (previousFrame, NUM_CHANNELS, audioBuffer, numSamplesRead / NUM_CHANNELS, interpolatedAudioBuffer, numSamplesToWrite / NUM_CHANNELS);
  39. aiffAppendFloatingPointSamples(aiff, interpolatedAudioBuffer, numSamplesToWrite, aiffFloatSampleType);
  40. }while(numSamplesRead == BUFFER_NUM_SAMPLES);
  41. aiffSaveWithFilename(aiff, "LinearInterpolation.aif");
  42. aiffDestroy(aiff);
  43. return 0;
  44. }
  45. void linearInterpolateBuffer(float* previousFrame, int numChannels, float* input, int inNumFrames, float* output, int outNumFrames)
  46. {
  47. int i, j, index;
  48. double distance, prevValue, nextValue;
  49. for(i=0; i<outNumFrames; i++)
  50. {
  51. for(j=0; j<numChannels; j++)
  52. {
  53. distance = i * (inNumFrames / (double)outNumFrames);
  54. index = ((int)distance) * numChannels + j;
  55. nextValue = input[index];
  56. prevValue = distance < 1 ? previousFrame[j] : input[index-numChannels];
  57. distance -= (int)distance;
  58. output[i*numChannels+j] = (nextValue-prevValue) * distance + prevValue;
  59. }
  60. }
  61. for(j=0; j<numChannels; j++)
  62. previousFrame[j] = input[(inNumFrames - 1) * numChannels + j];
  63. }
  64. void cubicInterpolateBuffer(float* previous3Frames, int numChannels, float* input, int inNumFrames, float* output, int outNumFrames)
  65. {
  66. int i, j, index;
  67. double distance, prev, prevPrev, next, nextNext;
  68. double a, b, c, d;
  69. for(i=0; i<outNumFrames; i++)
  70. {
  71. for(j=0; j<numChannels; j++)
  72. {
  73. distance = i * (inNumFrames / (double)outNumFrames);
  74. index = ((int)distance) * numChannels + j;
  75. nextNext = input[index ];
  76. next = distance < 1 ? previous3Frames[index + 2*numChannels] : input[index - numChannels];
  77. prev = distance < 2 ? previous3Frames[index + numChannels ] : input[index - 2*numChannels];
  78. prevPrev = distance < 3 ? previous3Frames[index ] : input[index - 3*numChannels];
  79. distance -= (int)distance;
  80. a = nextNext - next - prevPrev + prev;
  81. b = prevPrev - prev - a;
  82. c = next - prevPrev;
  83. d = prev;
  84. output[i*numChannels+j] = ((a * distance * distance * distance) + (b * distance*distance) + (c * distance) + (d));
  85. }
  86. }
  87. for(j=0; j<numChannels*3; j++)
  88. previous3Frames[j] = input[(inNumFrames - 3) * numChannels + j];
  89. }

Output:

Explanation of the Concepts

This example shows how to use the interpolation algorithms in the previous sections of this chapter. It example reads in the audio file "demo.aif", and transposes it down 21 half-steps by interpolating it.

Download Demo.aif

The most memory-efficient way to interpolate a longer file is to read a smaller number (say a few thousand) of samples out of the original file, interpolate them, add them to a new file, and then get the next few thousand samples, and so forth, until the end of the file. The algorithms in the previous example were designed to facilitate this technique, so this example demonstrates the proper use of those functions.

From a musical perspective, we are not usually concerned with the duration of a sound after it is interpolated, so much as we are concerned with the pitch of that sound. Since interpolation deals in durations (number of samples), we will need to convert. The duration os a sound is inversely proportional to its pitch (i.e. raising the pitch reduces the duration), so when we transpose, if we know the ratio of the original pitch to the target pitch, then we also know the ratio of their durations. In the tempered chromatic scale, the ratio is one note to the next is equal to twelfth root of two. An interval of say, 5 half-steps is created by raising 2 to the 5th (the number of half steps), and taking the twelfth root of that. Furthermore, (more high-school math) we remember that taking the 12th root of two is the same as raising two to the power of 1/12. So, if we want to transpose a sound by a certain number of half steps, we can find the ratio of their frequencies like so:

frequencyRatio = (2^numHalfsteps)^(1/12)

The ratio ratio of the duration of the original audio file to the interpolated audio file would just be one over this number. The mathematical shortcut for this, however, is just to raise 2 to the negative number of half-steps, so that the duration ratio can be found like so:

durationRatio = (2^-numHalfsteps)^(1/12)

Then, of course, multiplying the number of samples in the original audio buffer by the duration-ratio gives the number of samples that will be required in the interpolated buffer to transpose the sound by the given amount.