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

  1. //Scrubbing.c
  2. //gcc /usr/include/MKAudio/MKAiff.c Scrubbing.c -o Scrubbing
  3. #include <MKAudio/MKAiff.h>
  4. #include <math.h>
  5. #define PRERECORDED_AUDIO_PATH "/Users/michaelkrzyzaniak/Desktop/demo.aif"
  6. #define NUM_CHANNELS aiffNumChannels(preRecordedAudio)
  7. #define SAMPLE_RATE 44100
  8. #define BITS_PER_SAMPLE 16
  9. #define NUM_SECONDS 20
  10. #define PI 3.141592653589793
  11. #define GRAIN_DURATION 0.1
  12. #define GRAIN_OVERLAP 0.3
  13. const int framesPerGrain = GRAIN_DURATION * SAMPLE_RATE;
  14. const int numGrains = NUM_SECONDS / (GRAIN_DURATION * GRAIN_OVERLAP);
  15. #define GAIN 0.5
  16. int main()
  17. {
  18. MKAiff* preRecordedAudio = aiffWithContentsOfFile(PRERECORDED_AUDIO_PATH);
  19. if(preRecordedAudio == NULL) return 1;
  20. MKAiff* aiff = aiffWithDurationInSeconds(NUM_CHANNELS, SAMPLE_RATE, BITS_PER_SAMPLE, NUM_SECONDS);
  21. if(aiff == NULL) return 1;
  22. const int samplesPerGrain = NUM_CHANNELS * framesPerGrain;
  23. float grainBuffer [samplesPerGrain];
  24. float windowFunction[samplesPerGrain];
  25. int i, j;
  26. for(i=0; i<samplesPerGrain; i+=NUM_CHANNELS)
  27. for(j=0; j<NUM_CHANNELS; j++)
  28. windowFunction[i+j] = GAIN * 0.5 * (1-cos(2*PI*i/samplesPerGrain));
  29. //windowFunction[i+j] = sin( PI * (double)i / (double)samplesPerGrain );
  30. for(i=0; i<numGrains; i++)
  31. {
  32. aiffSetPlayheadToFrames(preRecordedAudio, i*aiffDurationInFrames(preRecordedAudio)/numGrains);
  33. aiffReadFloatingPointSamplesAtPlayhead(preRecordedAudio, grainBuffer, samplesPerGrain);
  34. for(j=0; j<samplesPerGrain; grainBuffer[j] *= windowFunction[j++]);
  35. aiffSetPlayheadToFrames(aiff, i * framesPerGrain * GRAIN_OVERLAP);
  36. aiffAddFloatingPointSamplesAtPlayhead(aiff, grainBuffer, samplesPerGrain, aiffFloatSampleType, aiffNo);
  37. }
  38. aiffSaveWithFilename(aiff, "Scrubbing.aif");
  39. aiffDestroy(aiff);
  40. aiffDestroy(preRecordedAudio);
  41. return 0;
  42. }

Output:

Explanation of the Concepts

This example uses granular synthesis to slowly scan or scrub over the contents of an audio-file.

Here, individual grains are pulled out of successive, overlapping locations in a single audio file. The following is a visual example of how the grains are made: the grey line represents the waveform of the original audio, red lines are individual window functions, and the yellow line represents the resulting individual grains.

WindowFunctions
Successive Grains


The grains are then spread out into a new audio file. This is similar to slicing a loaf of bread, and evenly fanning out the slices so that the loaf takes up more space on the platter. Notice that spreading the slices onto an even larger platter results in each slice overlapping the next by less distance. This can, of course be corrected by cutting the bread into more slices to begin with. In granular synthesis, by analogy, the number of grains on average that are sounding at any given time (the average number of overlapping slices of bread, if several cross-sections were taken) plays a large role in the final musical texture. This quantity is known as 'grain density', and is sometimes expressed in terms of 'grain overlap', which is defined as 1/grainDensity. Qualitatively, this means that if there are to be 2 grains sounding at any given time, then each will have to overlap the previous by 0.5 (50%). In granular synthesis, since we are usually are most interested in and the grain density (or grain overlap) and the final duration (the size of the platter), the number of grains to be taken from the original audio file (the number of slices) is usually calculated from these data.

Explanation of the Code

Code Explanation