P5.sound

This page shows how to accomplish many of the goals that are discussed in the sound unit, but with the native P5.sound library. The video playlist below shows how to achieve these goals as well.

P5’s Native Sound Library

P5 contains its own sound library that can be utilized to achieve many of the tasks this course utilizes Tone.js for. This library, P5.sound can be found here, and has its own reference material separate from the basic P5. Below are a few short instructions on how to achieve the goals of the sound unit with the native sound library.

Playback in P5.sound

You can first load in a sound file into a sketch identically to how we images on this page.

Then you can store the sound file inside a variable through the new loadSound() function.

let sample;

function preload(){
    sample = loadSound('sound_files/drumloop_1.mp3');
}

These sound files can either be local to the P5 sketch or remotely hosted as with the Tone.js library.

The final step in order to hear the sound is to utilize the .play() method like below.

function mousePressed(){
sample.play();
}

All of the same rules in regards to triggering a sound within the draw loop still apply when utilizing the P5.sound library.

One benefit of utilizing this library is that there is no need for the .toMaster() method that Tone.js requires. P5 will automatically connect the sound output to your computer’s master speakers.

Effects in P5.sound

P5.sound contains a few built in audio effects that can be utilized through the objects below. In general these objects are user friendly and effective, hut there is a significantly lower amount of effect when compared to the Tone.js library.

P5.Delay

The P5.Delay object is a feedback delay effect that can be applied to various sound sources. you can adjust how this delay works with the .process method

let delay;

function setup(){
    delay = new p5.Delay();
    delay.process(source, delayTime, feedback, filterFrequency)
}

Unlike in Tone.js, you can specify the source without utilizing the .connect() method. However, you can utilize this method in order to chain together multiple effects. Each of the arguments of the .process() method can be called on their own in order to individually update while the code is running.

The source is the sound file or signal the delay is being applied to. The delayTime is in seconds. Feedback amount is between 0 and 1. Filter frequency is in Hz.

P5.Reverb

P5.Reverb() adds a simple reverberation to the playback of a sound file or audio signal. Like P5.Delay(), you can set its initial parameters through the .process() method.

let verb;

function setup(){
    verb = new p5.Reverb();
    verb.process(source, reverbTime, decayRate)
}

The source is the incoming soundfile or audio signal. The reverb time is how many seconds the effect lasts for. The decay rate is the percentage that the sound decays over that time.

P5 also contains a convolution reverb that can mimic real-life spaces.

P5.Noise

P5.Noise() generates three different kinds of noise that can be utilized as an audio signal for various purposes.

let noise;

function preload(){
    noise = new p5.Noise('type');
}

You can set the noise type to be white, pink or brown by indicating the type as a string.

P5.Filter

P5.Filter() applies three different types of filters to audio signals and playback. These types can raise or lower various frequency areas in the sound.

let filter;

function preload(){
    filter = new p5.Filter(type)
}

You can specify the types of filter as a string:

  • lowpass: attenuates high frequencies.
  • highpass: attenuates low frequencies.
  • bandbass: attenuates frequencies on either side of a given center frequency.

You can also create these filters directly with the p5.LowPass(), p5.HighPass(), and p5.BandPass() objects.

You can also set the various parameters of your filter with the methods below:

  • .freq(): sets the center/cutoff frequency of the filter from 10-22050 Hz.
  • .res(): controlls the sharpness of the filter cutoff.
  • .toggle(): turns the filter on and off.

Other Effects

In addition to these effects, P5.sound also has a builtin distortion and compressor which can be viewed through the linked reference pages.

Connect and Disconnect

By default each of the objects listed above connects directly to your computer’s master sound output. However you can utilize the .disconnect() and .connect() methods in order to set your own signal pathways.

.disconnect() needs to be called first because this method disconnects the object from all of its previous output locations.

You can then call .connect(), which functions identically to .connect in Tone.js. You give the new connection destination as an argument.

let soundfile, delay, reverb;

function preload(){
    soundfile = loadSound('sound_files/example_file.mp3');
    reverb = new p5.Reverb();
    delay = new p5.Delay();

    delay.disconnect();
    delay.connect(reverb);
    soundfile.disconnect();
    soundfile.connect(delay);
}

Synthesizers in P5.sound

Oscillator

P5.sound contains a built in oscillator that can generate the 4 basic waveforms.

let osc;

function setup(){
    asc = new p5.Oscillator(freq, 'type')
}

To select the waveform, enter the type as a string: sine, triangle, square, or sawtooth You can optionally give the oscillator a starting frequency value. this value could be hard-coded if the pitch will never change, or a variable if you want the oscillator to be able to play different notes. The frequency is represented in Hz.

You can control the Oscillator uting the following properties:

  • .start() and .stop(): turn the oscillator on and off.
  • .amp(): controls the volume of the oscillator. Accepts values between 0 and 1.
  • .freq: sets the oscillator frequency.
  • .setType(): changes the waveform. accepts the values as a string.
  • .add() and .mult(): add or multiply the Oscillator.amp() value respectively in order to alter the volume.
  • .scale(): maps the amplitude values to a given range.

MonoSynth and PolySynth

P5.sound contains two main synthesizers that utilize the oscillator object.

MonoSynth plays a single note at a time and has these unique aspects:

  • .triggerAttack(): Triggers the note playback according to the ADSR envelope. The note will sustain until the .triggerRelease() command is given.
  • .triggerRelease(): ends a note by triggering the release portion of the ADSR envelope.

PolySynth can play multiple notes created by up to 8 MonoSynth objects at once and has these unique properties:

  • polyvalue: the number of voices in the synthesizer (from 1-8)
  • .noteAttack() and .noteRelease(): function identically to triggerAttack() and triggerRelease() in a MonoSynth. but can be uniquely applied to each individual note being played.
  • noteADSR(): can apply a unique ADSR envelope to each note being played.

Both of the synthesizers contain these shared methods:

  • .play(): triggers a note to play. automatically calls the note to release after a given sustain time (in seconds).
  • .setADSR(): seta the attack, decay, sustain, and release values of the notes to be played. each value is represented in seconds.
  • .dispose(): gets rid of the synthesizer in order to free up the memory space.

You can also connect these synthesizers to various audio effects through the .connect() and .disconnect() methods.

Sequences in P5.sound

You can set the BPM of the tempo for sequenced events with the setBMP() function. Is arguments are:

  1. BPM (number).
  2. seconds from now to change to the new BPM (number).

Once the BPM has been set, the one of the main structures for scheduling sequences in P5.sound is through the SoundLoop() object. A SoundLoop will call a callback function every time it starts over.

let loop, synth;
let loopLength = 30;
let notes = [60, 61, 62 , 63];

function setup(){
    synth = new p5.MonoSynth();
    loop = new p5.Soundloop(loop1, loopLength).start();
}

function loop1(){
    //musical triggers go here.
  let noteIndex = (soundLoop.iterations - 1) % notes.length;
  let note = midiToFreq(notes[noteIndex]);
  synth.play(note, 0.5, timeFromNow);
}

SoundLoops can have the following properties/methods:

  • .musicalTimeMode: can intrepret time as numbers, or in the same ways that Tone.js does when using strings.
  • .maxIterations: the maximum number of times the loop can repeat.
  • .timeSigniture: number of beats per measure.
  • .interval: length of each loop
  • .iterations: times the loop has repeated.
  • .start(), .stop(), and .pause(): control playback of the loop.
  • .syncedStart(): syncs up multiple loops. Argument is the loop you want to sync with.

Phrase, Parts, and Score

In addition to loops you can also build collections of musical phrases in P5.sound. A Phrase is a collection of multiple events such as a sequence, Parts are collections of one or more phrases, and a Score is a collection of multiple Parts. each element can play and order its components within itself. So a part can play multiple phrases in a sequence, and a score can play multiple parts in a sequence.

To generate a phrase you must first create it with the following parameters

let phrase1 = new p5.Phrase(name, callback, sequence);

You need to give each phrase a name so that you can refer to it later in your code. The callback is the function that plays the musical notes, and usually takes arguments for the time to play the note and a value from the sequence array. The last parameter is the sequence array, which contains the notes to be played in the phrase. These values can vary depending on the parameters of the callback function, but the value 0 always indicates a rest for the given beat within the sequence. You can add additional functionality to the callback in order to program various parameter changes as well.

In order to hear a phrase, you must first add it into a part with the addPhrase() method.

let part, phrase

phrase = new p5.Phrase()
part= new p5.Part()

part.addPhrase(phrase)

Parts will play multiple phrases, and can be controlled with the following methods:

  • .start(), .stop(), and .pause(): control the playback of the part.
  • .setBPM(): sets the speed of the part.
  • .loop() and .noLoop() tell the part to start and stop looping its phrases respectively.
  • .addPhrase() and .removePhrase(): change the contents of a part
  • .replaceSequence(); changes specified sequences within a part.

A Score will play multiple parts in sequence. Once you have loaded multiple parts, you can order them within the Score object like this:

let score;

score = new p5.Score(part1, part1, part2, part1, part3, part2...)

You can place the parts in any order you want and repeat parts. You can also control the playback, looping, and tempo of the score by using the same methods listed above for parts.