Getting spectral data from sound

PCM sound representation is good for sound storage and playing. It lets us operate sound samples like a piece of magnetic tape—to cut, shuffle its parts, reverse, and glue back together. Also it lets us change and measure the overall volume of the sound. But PCM is inadequate for more advanced sound analysis and processing. The reason being that humans cannot hear separate audio samples, only frequencies in sound in short time intervals. The collection of amplitudes of each frequency in a short time interval is called spectrum of the sound. Therefore, sound processing methods should work using frequencies-spectrum language. This differs sound processing from image and video processing as they work well with pixels independently.

In this section, we will not dip into the mathematical aspects of spectrum computing, but will learn how to compute it using the openFrameworks functions and use it in projects.

The spectrum in openFrameworks is calculated for sound, which is formed by playing samples using the ofSoundPlayer objects.

Tip

If you need to calculate the spectrum for your own generated audio with the audioOut() function, you need to do it by yourself, with Fast Fourier Transform implementation, which you can find on the Internet.

The spectrum is an array of float numbers. It is obtained using the ofSoundGetSpectrum( N ) function, where N is the number of spectrum bands. Normally, N is the power of two:

float *val = ofSoundGetSpectrum( 256 );

Here val is an array of size 256. First array items correspond to lower frequencies, and last array items correspond to higher frequencies. With increasing N, you will have a more detailed description of the spectrum, but the data accuracy in time will decrease. The values of the spectrum are normalized so you can think they lie in the range [0, 1], though for loud sounds, values can exceed 1.

Tip

Note that you should not release the memory of val, because it is managed by a sound engine.

Having a spectrum array, you can get its values and use these for changing the controlling parameters for physics and visualization in your project. It is a good idea to smooth the spectrum because it jitters. Also, when using it for crucial projects, you do not need to use one spectrum band but perform smoothing (filtration) spectrum values over a number of bands.

The technology of filtering the regions of a spectrum is widely used in the VJ software for detecting a track's BPM (beats per minute, or tempo), and tracking separate beats of drums and other instruments. If you need to do a really advanced sound analysis of the music track for visualization, it may be a good idea to use Max/MSP, VDMX, or any other VJ software for analysis, and then send its result to your openFrameworks project via the OSC protocol. For more details on using the OSC protocol, see Chapter 11, Networking.

Dancing cloud example

This is an example of an audio-reactive visual project. We are going to play a music track, get its spectrum, and use it for controlling point cloud parameters. So the cloud is rendered on the screen and shakes synchronously with drum beats in the music.

Note

This is example 06-Sound/06-DancingCloud.

The example is based on the emptyExample project in openFrameworks. Before running it, copy the surface.wav file into the bin/data folder of your project.

In the testApp.h file, in the class testApp declaration, add declarations of a sound sample:

  ofSoundPlayer sound;  //Sound sample

Now let's consider the testApp.cpp file. For simplicity, we place the constants and variables not in class testApp definition, but right into the cpp file, after the #include "testApp.h" line:

const int N = 256;     //Number of bands in spectrum
float spectrum[ N ];   //Smoothed spectrum values
float Rad = 500;       //Cloud radius parameter
float Vel = 0.1;       //Cloud points velocity parameter
int bandRad = 2;       //Band index in spectrum, affecting Rad value
int bandVel = 100;     //Band index in spectrum, affecting Vel value

const int n = 300;     //Number of cloud points

//Offsets for Perlin noise calculation for points
float tx[n], ty[n];
ofPoint p[n];          //Cloud's points positions

float time0 = 0;       //Time value, used for dt computing

You can see that the spectrum is stored in the spectrum array, with size N = 256. Cloud has two control parameters—radius Rad and velocity Vel. Radius depends on the spectrum band bandRad = 2, and velocity depends on spectrum band bandVel = 100. These bands were selected specifically for the given music track, so Rad and Vel jump up on the base drum and snare drum beats respectively. Visually, the cloud expands on the base drum beat, and the points in the cloud begin to shuffle on the snare drum beat. The cloud is made from array points p, with size n = 300. Points are moved by Perlin noise (see more details in Appendix B, Perlin Noise).

The setup() function does sound sample loading and sets Perlin noise offsets for points initialization:

void testApp::setup(){
  //Set up sound sample
  sound.loadSound( "surface.wav" );
  sound.setLoop( true );
  sound.play();

  //Set spectrum values to 0
  for (int i=0; i<N; i++) {
      spectrum[i] = 0.0f;
  }

  //Initialize points offsets by random numbers
  for ( int j=0; j<n; j++ ) {
      tx[j] = ofRandom( 0, 1000 );
      ty[j] = ofRandom( 0, 1000 );
  }
}

The update() function gets the spectrum of the currently played sound, computes its smoothed values to the spectrum array, and recalculates the Rad and Vel parameters. Finally, it calculates new point positions:

void testApp::update(){
  //Update sound engine
  ofSoundUpdate();
  
  //Get current spectrum with N bands
  float *val = ofSoundGetSpectrum( N );
  //We should not release memory of val,
  //because it is managed by sound engine

  //Update our smoothed spectrum,
  //by slowly decreasing its values and getting maximum with val
  //So we will have slowly falling peaks in spectrum
  for ( int i=0; i<N; i++ ) {
      spectrum[i] *= 0.97;    //Slow decreasing
      spectrum[i] = max( spectrum[i], val[i] );
  }

  //Update particles using spectrum values

  //Computing dt as a time between the last
  //and the current calling of update()
  float time = ofGetElapsedTimef();
  float dt = time - time0;
  dt = ofClamp( dt, 0.0, 0.1 );
  time0 = time; //Store the current time

  //Update Rad and Vel from spectrum
  //Note, the parameters in ofMap's were tuned for best result
  //just for current music track
  Rad = ofMap( spectrum[ bandRad ], 1, 3, 400, 800, true );
  Vel = ofMap( spectrum[ bandVel ], 0, 0.1, 0.05, 0.5 );

  //Update particles positions
  for (int j=0; j<n; j++) {
      tx[j] += Vel * dt;  //move offset
      ty[j] += Vel * dt;  //move offset
      //Calculate Perlin's noise in [-1, 1] and
      //multiply on Rad
      p[j].x = ofSignedNoise( tx[j] ) * Rad;
      p[j].y = ofSignedNoise( ty[j] ) * Rad;
  }
}

The draw() function draws a spectrum and the cloud. Cloud's points are rendered as small circles. Additionally, pairs of points with distance less than the threshold dist = 40 are joined by a line segment:

void testApp::draw(){
  ofBackground( 255, 255, 255 );  //Set up the background

  //Draw background rect for spectrum
  ofSetColor( 230, 230, 230 );
  ofFill();
  ofRect( 10, 700, N * 6, -100 );

  //Draw spectrum
  ofSetColor( 0, 0, 0 );
  for (int i=0; i<N; i++) {
      //Draw bandRad and bandVel by black color,
      //and other by gray color
      if ( i == bandRad || i == bandVel ) {
          ofSetColor( 0, 0, 0 ); //Black color
      } else {
          ofSetColor( 128, 128, 128 ); //Gray color
      }
      ofRect( 10 + i * 5, 700, 3, -spectrum[i] * 100 );
  }

  //Draw cloud

  //Move center of coordinate system to the screen center
  ofPushMatrix();
  ofTranslate( ofGetWidth() / 2, ofGetHeight() / 2 );

  //Draw cloud's points
  ofSetColor( 0, 0, 0 );
  ofFill();
  for (int i=0; i<n; i++) {
      ofCircle( p[i], 2 );
  }

  //Draw lines between near points
  float dist = 40;    //Threshold parameter of distance
  for (int j=0; j<n; j++) {
      for (int k=j+1; k<n; k++) {
          if ( ofDist( p[j].x, p[j].y, p[k].x, p[k].y )
                       < dist ) {
              ofLine( p[j], p[k] );
          }
      }
  }

  //Restore coordinate system
  ofPopMatrix();
}

When running this example, you will hear a music track and will see the moving point cloud in the center of the screen. At the bottom of the screen, you will see the sound spectrum:

Dancing cloud example

You can see that spectrum bands bandRad = 2 and bandVel = 100 are drawn in black (numeration from 0). Note that band 2 jumps on the base drum beat, and band 100 jumps on the snare drum beat, but in a lesser range, and the cloud extends and shuffles in correspondence to these beats.

You can extend the example by associating the radius and color of the point's circles with some spectrum bands.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset