ANS art work

IngoognI

A blog.

ANS is a optoelectronic sound synthesizer invented by Yevgeny Murzin. He named his device after Alexander Nikolayevich Scriabin (ANS)

ANS Ingo

Joseph Fourier stated that a signal can be broken down in a series of sine waves. This is the base of ANS, where the opposite is done. The amplitudes of many sine waves of different frequencies are modulated. The results are then added to get one signal.

With modern technology this can all be done with our computers. Images can be converted to sound. Inverse Fourier transforms make it a relative quick process. There is software that can do this, Virtual ANS for example.

In POV-Ray there is no FFT, so I have to use brute force. That keeps thing simple. 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.

Frequencies

Instead of scanning images, I'm going to sample functions created in POV-Ray. These can be rendered to sound and to an image. The sampled region is [<0,0>, <1,1>]. The y-axis represents the frequencies and the x-axis is time.

ANS art work
A POV-Ray rendered ANS sample.

Distribution

The y-axis are frequencies sounds simple, but there is a bit more to that. Each position on the y-axis is actually a sine wave oscillator that emits a single frequency. The amplitude of that frequency is changed depending on the outcome of the functions that we'll sample.

The distribution of the oscillators along the y-axis is not equidistant.

octaves = log2 ( f1/f2 )

The equation above shows the relation between octaves and frequencies. Based on that relation positions of oscillators (frequencies) on the y-axis can be interpolated:

#declare Pos2Freq = function(Pos, Min, Max){
    (Max-Min) 
  * (Min * pow(2, Pos * (log(Max) - log(Min)) / log(2)) - Min) 
  / (Min * pow(2, (log(Max) - log(Min)) / log(2)) - Min) + Min 
}

Pos:
The position on between min and max. The position is given in a range [0,1].
Min:
The minimum frequency of the range.
Max:
The maximum frequency of the range.

The Range

Now the way to distribute the frequencies is implemented let's look the minimum and maximum frequency values. The starting point is the range of frequencies to cover. The number of octaves. Then decide on how many bands the octaves have to be divided in.

With these we calculate the vertical resolution. The amount of oscillators that are needed. It's just as with images. The higher the resolution the better the quality.

Then set a minimum frequency and calculate the maximum. You can of course go from 20Hz to 20kHz but effects from the sound function can result in values outside that range. Going over the Nyquest frequency (halve the sample frequency) can have unwanted results.

#declare Octaves = 9;
#declare OctaveRes = 60;
#declare Vres = Octaves * OctaveRes;
#declare Fmin = 30;
#declare Fmax = pow(2, Octaves) * Fmin;

No array

Ideally one would pre-calculate the frequencies once for the oscillators and put them in an array. The thing is that POV-Ray does not support arrays in functions.

Splines are supported in function and can be used as arrays, even if it is not their intended purpose. It works fine.

#declare FreqPos = function {
  spline{
    #for(i, 0, Vres)
      i, <Pos2Freq(i / Vres, Fmin, Fmax), 0>,
    #end
  }
} 
#undef i

Sampling

The x-axis is the time line. For now I have chosen to let the time line go from 0 to 1, regardless of how long the track will be. Simmilar to how we deal with animations in POV-Ray with a clock going to one instead of counting frames. I might change my mind on that.

The straight forward way is to set a Duration and multiply it by the SampleRate. The result is the amount of Samples, or Ticks that are needed.

With Samples and SampleRate the usual tick until Samples loop can be build and the data from the inner function(s) can be written to a wave file.

ANS art work
A POV-Ray rendered ANS sample.

The sampling function

For every Tick on the time line we have to sample every oscillator on the frequency axis. For every oscillator on the frequency axis we have to sample the sound function and use the result of that as amplitude of the oscillator. Then all the oscillator results for this whole column have to be added.

POV-Ray has a nice sum() function for that. This I used to do all the column related operations.

#declare ColVal = function(Vr, nSamples, Tick, SampleRate){
   // #for(i, 0, Vr, 1) sum(SinOsc(.i...)) #end
   sum(
    i, 0, Vr, 
    SinOsc(
      FreqPos(i).x, 
      0, 
      fSound(Tick/nSamples, i/Vr, 0)/Vr, 
      Tick, 
      SampleRate
    )
  )
};

Vr:
The vertical resolution, actually the number of frequency bands to be generated. (I should change this to FreqRes)
nSamples:
The total of samples to be generated
Tick:
One step that adds a sample
SampleRate
Samples per second of digital audio

Note: The result of fSound() is divided by the number of samples. As the sine waves are not coherent a division by pow(Vr, 0.5) may suffice. There could be a problem with that at the very beginning of the sound when the Phase of the sine waves is not randomised. Something to investigate.

The sound function

Inside the above ColVal function lives the sine oscillator. Inside that lives the sound function fSound

First lets get the good old SinOsc and have a look at that:

#declare SinOsc = function(Freq, Phase, Amp, Tick, SRate){
  Amp * sin((Tick * TAU * (Freq/SRate)) + Phase)
};

The first parameter is the frequency. That we get from the spline function FreqPos() we defined earlier. We leave the phase at 0.

Note: While creating the spline the y-value of the vector could be randomised between -pi and pi. This value could be used for the Phase. Then not all sine oscillators would start at zero.

The Amplitude. Now it gets interesting. Here we put the sound function and it has to return a single value. When using a colour function a .gray after the function: fSound(Tick/nSamples, i/Vr, 0).gray/Vr

Anything that POV-Ray allows inside functions can be used. Or rebuild parts of this to use macros and use the trace function to sample a scene.

Download: A scene file to render ANS sounds is added to the POVSound.zip file.

Zugabe

While fiddling with all this it occurred to me that a function set can be created that is a additive sine oscillator. Again, based on the spline:

#declare FreqPos = function{
  spline{
    0,   <120,0,1>
    0.5, <250,0,1>
    1,   <500,0,1>
  }
}

#declare SinAddOsc = function(FreqRes, Amp, Tick, SampleRate) {
  Amp * sum(
    i, 0, FreqRes,
    SinOsc(
      FreqPos(i/FreqRes).x, 
      FreqPos(i/FreqRes).y, 
      FreqPos(i/FreqRes).z/FreqRes, 
      Tick, 
      SampleRate
    )
  )
};

The spline defines the frequency, phase and amplitude of the individual oscillators. You can stick strictly to the defined points. Yet, the nice feature that the spline adds is the interpolation between the defined points. For example, only change the middle t value form 0.5 to 0.2. And of course instead of sum prod can be used, but remove the division by FreqRes of the amplitude.