- //PulseWave.c
- //gcc MKAiff.c PulseWave.c -o PulseWave
- #include "MKAiff.h"
- #define SAMPLE_RATE 44100
- #define NUM_CHANNELS 1
- #define BITS_PER_SAMPLE 16
- #define NUM_SECONDS 3
- const int numSamples = NUM_SECONDS * NUM_CHANNELS * SAMPLE_RATE;
- int main()
- {
- MKAiff* aiff = aiffWithDurationInSeconds(NUM_CHANNELS, SAMPLE_RATE, BITS_PER_SAMPLE, NUM_SECONDS);
- if(aiff == NULL) return 0;
- float audioBuffer[numSamples];
- double frequency = 440;
- int wavelength = SAMPLE_RATE / frequency;
- float pulseWidth = 0.9;
- int i;
- for(i=0; i<numSamples; i+=NUM_CHANNELS)
- {
- if((i/NUM_CHANNELS % wavelength) < (wavelength*pulseWidth))
- audioBuffer[i] = 1;
- else
- audioBuffer[i] = 0;
- }
- aiffAppendFloatingPointSamples(aiff, audioBuffer, numSamples, aiffFloatSampleType);
- aiffSaveWithFilename(aiff, "PulseWave.aif");
- aiff = aiffDestroy(aiff);
- return 1;
- }
Output:
Explanation of the Concepts
A pulse wave is similar to a square wave. The primary difference is that a pulse-wave oscillates between 0 and 1, whereas a square wave typically oscillates between -1 and 1. The other primary difference is that the value a square wave is high for half of its period, and low for the other half, whereas the value of a pulse wave can be high for any arbitrary percent of its period, and low for the rest. The ratio of the high part of the period to the duration of the entire period is known as the 'duty-cycle' or the 'pulse-width', and is typically expressed as a percentage. A square wave has a duty-cycle of 50%, because it is high for 50% of its period. For comparison, here are some plots of a pulse wave with a 50% duty cycle:
a 10% duty-cycle:
and a 1% duty-cycle.
For analog applications, pulse waves are often used to trigger other events, and the pulse-width (duty-cycle) can control the relative duration of that other event. When used as an audio oscillator, the pulse-width has a marked effect on the timbre of the sound. Of course, a pulse-wave with a 50% duty cycle has almost the same timbre as a square wave (although it is quieter due to the reduced amplitude), with exclusively odd-numbered partials. Notice, however, that the pulse-wave also has a direct-current (0 Hz) component, because the wave is, on average, not centered about the x axis, but is raised above. This direct-current component is called 'DC offset'. Here is a Fourier spectrograph of such a wave that shows the strong odd-numbered partials and shows the DC offset as a spike on the far left:
The duty-cycle, here 50%, may also be expressed as a fraction, 1/2. Notice that the denominator is 2, and every second partial is missing from the wave's spectrum. A similar principal holds true for other duty cycles as well. For instance, a pulse wave with a duty-cycle of 1/3 is missing every third partial:
a pulse wave with a duty-cycle of 1/4 is missing every fourth partial:
and a pulse wave with a duty-cycle of 1/5 is missing every fifth partial:
and so forth. This series of examples shows that as the duty cycle moves from 50% towards 0%, the resultant audio spectrum becomes increasingly rich. The same is true as the duty-cycle moves from 50% towards 100%. In fact, the timbre may be said to be symmetrical about the 50% mark: a pulse wave with a duty-cycle of 40% has a spectrum that is identical to a pulse wave with a duty-cycle of 60%, because they both deviate from 50% by 10%; etc... This makes sense, because a 60% duty-cycle pulse-wave is just a 40% duty-cycle pulse-wave upside-down. The only difference is that higher duty-cycles will produce more DC-offset, because they spend more time, on average, above 0. The timbre of pulse-waves with greater deviation from 50% are often described as being 'fuller' than those with lesser deviations. To me they sound more nasal. The above images are linked to the recordings that generated them for scrutiny.
Explanation of the Code
The duty-cycle, or pulse-width, of the waveform is declared on line 22, and is expressed as a multiplicative constant (0.9) rather than a percentage (90%). Line 27 calculates the number of samples in the 'high' part of the waveform, according the duty-cycle:
(wavelength * pulseWidth) and checks to see if the current-sample: (i/NUM_CHANNELS) of the current repetition of the wave: % wavelength is within the high portion. If it is, the value of the current samples is set to 1 on line 28, otherwise it is set to 0 on line 30.