Play Sounds with Processing

Updated: Apr 16

Want to be able to play tones within a Processing sketch? Read this post to see how the TriOsc library can be used add a layer of sound to your projects.

[Disclaimer: As an Amazon Associate I earn from qualifying purchases from paid links in the article]

Introduction

Sonic feedback from your computer program can be just as important as visual feedback. Also, depending on your use case, sound may be an integral part of the application's whole purpose for existence. Interested in game development? Then you'll need some sound effects. See the Breakout with processing tutorial post for an example of this use case.


Below is a video of the output produced by the final program in this post:

Examples

TriOsc Libary in Processing

This library comes included along with Processing3, with documentation found here. The basic example provided in the documentation shows how to create a triangle wave oscillator, but doesn't go into any detail on how to specify actual frequencies, notes, or rhythms.


Here is basic example to play the note A at a frequency of 440 Hz

for 0.25 seconds:


Processing Sketch

import processing.sound.*;
float attackTime = 0.0;
float sustainTime = 0.25;
float sustainLevel = 1.0;
float releaseTime = 0.0;

TriOsc triOsc;
Env env;

void setup() {  
  env  = new Env(this);
  triOsc = new TriOsc(this); 
  triOsc.play(440,0.5);
  env.play(triOsc, attackTime, sustainTime, sustainLevel, releaseTime);   
}

void draw() {} 

Expand on the basic example:


Now we are going to use the basic building block above to create a more complex sketch that plays the C major scale forwards and backwards (screen recording of program output shown in the introduction above)

import processing.sound.*;

float attackTime = 0.001;
float sustainTime = 0.25;
float sustainLevel = 1.0;
float releaseTime = 0.05;
float trigger = 0.0;
float next = 1;

TriOsc triOsc;
Env env;
float[][] song = {{60.0, 1.0}, {62.0, 1.0}, {64.0, 0.25}, 
  {65.0, 1.0}, {67.0, 0.5}, {69.0, 1.0}, 
  {71.0, 0.5}, {72.0, 1.0}};
int note = 0;

void setup() { 

  size(900, 400);
  // Create triangle wave and envelope 
  triOsc = new TriOsc(this);  
  env  = new Env(this);
}

void draw() {

  if ((millis() > trigger)) {

    float freq = midiToFreq(song[note][0]);    
    triOsc.play(freq, 0.5);    
    env.play(triOsc, attackTime, secToMilli(song[note][1]),   sustainLevel, releaseTime); 
    trigger = millis() +  secToMilli(song[note][1]);
    background(0);
    textSize(45);
    textAlign(CENTER);   
    fill(255, 0, 0);

    text("Midi Note: " + String.valueOf(song[note][0]), width / 2,    height / 6) ;   
    fill(0, 255, 0);
    text("Duration: " + String.valueOf(song[note][1]) + " second(s)", width / 2, height / 3) ;   
    fill(0, 0, 255);
    text("Frequency: " + freq, width / 2, height / 2) ;

    // Change note direction if necessary
    if (note == song.length - 1) {
      next = -1.0;
    } else if (note == 0) {
      next = 1.0;
    } 
    note += next;
  }
} 
// Convert from Midi note to frequency 
// Formula:  f = 440 * 2^(n-69)/12
float midiToFreq(float note) {
  return (pow(2, ((note-69)/12.0)))*440;
}

// Convert seconds to milliseconds
float secToMilli(float sec) {
  return (sec * 1000.0);
}

Details to note:


  • float midiToFreq(float note) - We are using the Midi Specification to map Midi notes to their actual frequencies. So rather than having to directly input the value for a Middle C of 261.63 Hz, this function would take the Midi value of 60 and convert it to 261.63, using the formula f = 440 * 2^(n-69)/12.


  • float[][] song = {{60.0, 1.0}, {62.0, 1.0}, ...}; - This is a 2-dimensional array which holds the Midi number and duration for the note in seconds. Through the use of helper function "float secToMilli(float sec)" this value gets converted to the milliseconds required by the "env.play()" function.