Swing POV-Ray Swing Ingo 2021-04-04
An oscillators creates a periodic signal swinging between a maximum and minimum value. This periodic signal can have many shapes. I created an include file with the basic wave forms. I'll also show two ways to design oscillators with complex wave shapes. But first,
A word of warning
MIND YOUR EARS!
Experiments with sound synthesis can create extremely loud and nasty sounds.
Don't use headphones! Turn your amps down! Build attenuators into your scripts!
Check the resulting waveform in an audio editor before listening. Audacity or Ocenaudio for example. Both support multiple platforms.
Parameters
Most oscillators here have the same set of parameters:
- Freq:
- (float, Hz) frequency of wave form.
- Phase:
- (float, radians) offset of the starting point of the wave form.
- Amp:
- (float) amplitude of the wave form.
- Tick:
- (float) one step for progressing the wave form.
- SRate:
- (int, Hz) sample rate. For CD quality 44100 Hz.
Note: TAU
is used in the include file. TAU = 2 * pi;
SineOsc
The sine wave oscillator showed up a few times before. Besides generating sine waves, it is also a useful building block to create other oscillators. The one in the next paragraph, the square wave oscillator is one of them.
/*:Sine wave oscillator*/ #declare SinOsc = function(Freq, Phase, Amp, Tick, SRate){ Amp * sin((Tick * TAU * (Freq/SRate)) + Phase) };
SquareOsc
There are quite a lot of ways of generating a square wave oscillator. This one is based on the sine wave and follows the sign of the sign wave function. It skips all intermediate values and flips from maximum +1 to minimum -1 and back. (the sgn
function comes from the math.inc
standard include file)
The square wave is a special case of the pulse oscillator, with a duty cycle 0f 50%, as I'll show later on
/*:Square wave oscillator*/ #declare SquareOsc = function(Freq, Phase, Amp, Tick, SRate){ Amp * sgn(SinOsc(Freq, Phase, 1, Tick, SRate)) }
Ramp, a building block
The ramp function is a major building block in creating oscillators. It goes from 0 to 1 in one period and then drops back to 0.
The ramp function, or phasor, is never used directly as all its energy is delivered on one side of the x-axis. This means a high direct current component in the signal and that's not something the voice coils of loudspeakers like.
/*:Ramp Oscilator, or Phasor, goes from 0 to 1 over 1 Frequency period*/ #declare Ramp = function(Freq, Phase, Tick, SRate){ mod(Tick + ((Phase/Freq)/(TAU)) * SRate, SRate/Freq) * Freq/SRate }
SawOsc
The simplest oscillator based on the ramp wave is the saw tooth. It is just a ramp function translated down to get an even spread on the positive and negative side.
The sawtooth has a wide and rich harmonic spectrum.
/*:Sawtooth oscillator*/ #declare SawOsc = function(Freq, Phase, Amp, Tick, SRate){ Amp * (Ramp(Freq, Phase, Tick, SRate) * 2 - 1) }
PulseOsc
The pulse oscillator is like a square wave oscillator, but with a variable duty cycle. The duty cycle runs from 0 to 100%. At the outer limits there is no oscillation. A flat signal at min or max is the result. Avoid these extreme values.
The code shows why the Ramp
function is so useful. Look at the SelectDuty
sub-function. R
is the ramp function result, D
the duty cycle. You can read this code as, "while the ramp function is smaller than the duty cycle value the result is +1. Past that point the result is -1."
/*:Pulse wave oscillator*/ #local SelectDuty = function(R,D){ select( R - D, 1, -1 ) } #declare PulseOsc = function(Freq, DutyCycle, Phase, Amp, Tick, SRate){ Amp * (SelectDuty(Ramp(Freq, Phase, Tick, SRate), DutyCycle/100)) }
TriOsc
In creating the triangle waves the same method as in PulseOsc is used. It is taken a bit further by chopping the ramp up in more pieces. Is the result of the ramp smaller than 0.25, is it between 0.25 and 0.75 or is it bigger than 0.75?
/*:Triangle wave Oscilator*/ #declare TriSelect = function(R) { select( R - 0.25, R, select( R - 0.75, 0.5 - R, R - 1 ) ) }; #declare TriOsc = function(Freq, Phase, Amp, Tick, SRate) { 4*Amp*TriSelect(Ramp(Freq, Phase, Tick, SRate)) };
SawSinOsc
And again the ramp wave is used. Now a section of it is used to implant a half sine wave when the result is smaller than 0.5. Above that a saw tooth is used.
This method of using segments of the ramp function for other functions is a powerful possibility to build complex wave forms. Yet I find coding these using the select
function in POV-Ray tedious. In the next section I show an alternative, although both methods will not give you the same results.
/*:SawSin oscillator, half a period sine wave, halve a period saw tooth*/ #declare SawSinSelector = function(R, S){ select( R - 0.5, S, R * 2 - 1 ) } #declare SawSinOsc = function(Freq, Phase, Amp, Tick, SRate){ Amp * ( SawSinSelector( Ramp(Freq, Phase, Tick, SRate), SinOsc(Freq, Phase, 1, Tick, SRate) ) * 2 - 1) };
Spline oscillators
With splines you can "draw" your own wave forms. Guess what, we use the ramp function results as spline parameters to query for the position vector. The result is a vector, but the oscillator can only deal with a float. So I take the .x value of the resulting output.
It is not possible to pass a spline as a parameter to a function. An in this case it is a good thing. It means you have to create one specific oscillator for one spline and things won't get mixed up. An example in the code below.
#declare SPL = function{ spline { linear_spline 0.00, <0.25,0> 0.25, <-1,0> 0.50, <1,0> 0.98, <-1,0> 1.00, <0.25,0> } }; #declare SplineOsc = function(Freq, Phase, Amp, Tick, SRate){ Amp * SPL(Ramp(Freq, Phase, Tick, SRate)).x }
Points of attention
When designing a spline oscillator, keep an eye on the maximum and minimum output values. Keep them within the [-1,1] range with a built in attenuator. Especially for the curvy splines, when all control points are within the limits, the curve can still escape them.
The spline is driven by the ramp function, that is set at a certain frequency. When your spline crosses the x-axis more tan once the pitch you'll hear is above the set frequency. This does not mean you should only cross once. Go wild.
Steep angles make richer sounds with more harmonics. Discontinuities in the curve also do this. Sharp discontinuities result in harsh sounds. For smooth sounds take care that the endpoints of the curves connect smoothly
Multiple splines
A thought that occurred to me while writing this and looking at the SplineOsc. A spline
can have five dimensions, so we can define five different wave forms within it. Instead of selecting the .x
as in the example, there is the choice of .x .y .z .filter .transmit
. It can be automated. It can be modulated. It can be randomised.
Now imagine that the five dimension for one t
value also form a spline. Or, can be interpolated as a spline. This means we can morph from one wave form to another with just one container object and two "clock" values, t0, t1
.
Thinking of that, two variables, u, v
. A parametric function to pick values from a 5D spline. Feels like we're in Wave Terrain Synthesis again. Just ideas. Something to figure out in time.
Visualisation and files
For your eyes
A quick and simple oscilloscope scene file for visualising the wave forms. The sample rate sets the resolution. Higher frequencies need higher sample rates. But the scene always only show five cycles. Keep the frequency low ~20Hz.
#declare nWaves = 5; #declare Freq = 20; #declare Dur = nWaves/Freq; #declare SampleRate = 10000; #declare Samples = Dur * SampleRate; #declare Tick = 0; #while (Tick < Samples) #declare Sample = SawOsc(Freq, 0, 1, Tick, SampleRate); sphere{ <(Tick/Samples)*nWaves, Sample, 0>, 0.02 pigment{rgb 1} } #declare Tick = Tick + 1; #end cylinder{ <0,1,0.1>, <nWaves+0.5,1,0.1>, 0.01 pigment{rgb<1,0,0>} } cylinder{ <0,0,0.1>, <nWaves+0.5,0,0.1>, 0.01 pigment{rgb<1,0,0>} } cylinder{ <0,-1,0.1>, <nWaves+0.5,-1,0.1>, 0.01 pigment{rgb<1,0,0>} } cylinder{ <0,-3,0.1>,<0,3,0.1>, 0.01 pigment{rgb<1,0,0>} } light_source{<0,0,-2500> color rgb 1} camera { angle 3.5 location <nWaves/2, 0,-100> right x*image_width/image_height look_at <nWaves/2, 0, 0.0> }
Files
The povosc.inc include file has a demo scene at the end of it. You can select to render sound or image. Run the povosc.inc file as it it were a .pov file. The waveheader.inc file is required to render audio and is included in the zip file
Download POVSound.zip