To include multiple source files, you only need to insert additional script lines in your index.html. The following does not use sketch.js, but instead runs two files — mybigproject.js and drawingfunctions.js:

The resulting program will consist of all the top-level (global) function and variable definitions. The preload, setup, and draw functions will be called as usual, regardless of where they are defined.

Music Synthesis

Beginning in the early 20th C, “analog computers” were invented to perform simulation of physical systems. These “computers” often consisted of modules that could be interconnected to simulate different systems. Based on this approach, some early sound “synthesizers,” especially by Donald Buchla and Robert Moog in the early 1960’s, introduced a variety of modular sound generators and processors that could be interconnected to construct and shape sounds in a flexible way.

Even prior to this, in 1957, Max Mathews created software that also had modules to perform different functions. Nearly every synthesizer since that time either consists of multiple modules or is explained as if there are multiple modules even when flexible (re)configuration is not possible.

p5.js includes objects for sound processing that follows this tradition.

Basic Tone Generation with p5.Oscillator

The most basic sound generator is the oscillator, which generates a tone. You can control the pitch and amplitude of the tone, and also the “wave shape” which determines whether the tone sounds simple or bright and buzzy.

Like all the modules we’ll see, you use new to make a new instance of the module, then call methods to set properties such as freq(uency) (pitch) and amp(litude) (how loud, how strong).

Notice you must call the start() method to start the oscillator. It’s best to leave things running and turn the sound on and off by changing the amplitude (see this in draw()).

Oscillator - press mouse to play

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

varosc;

functionsetup(){

createCanvas(400,400);

osc=newp5.Oscillator();

osc.setType('sine');

osc.freq(660);

osc.amp(0.0);

osc.start();

}

functiondraw(){

background(200);

if(mouseIsPressed){

osc.amp(0.1);

}else{

osc.amp(0.0);

}

}

Envelopes

Musical tones do not typically start suddenly and end suddenly. The “shape” of the sound is called the “envelope”, which typically starts quickly (but not instantaneously) and tapers off at the end. Carefully controlled/designed shapes help make musical tones expressive. Here, we create an Envelope (Env) object and use it to control the amplitude of an oscillator.

The setADSR method lets you make a simple envelope with control over Attack time, Decay time, Sustain level, and Release time.

Note that the triggerAttack method runs the envelope through to the sustain portion, and triggerRelease finishes the envelope with a release that brings the amplitude back to zero (silence).

Envelope controls oscillator tone

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

varosc,env;

functionsetup(){

createCanvas(400,400);

osc=newp5.Oscillator();

osc.setType('sawtooth');

osc.freq(220);

env=newp5.Env();

env.setADSR(0.01,1.2,0.3,0.2);

osc.amp(env);// envelope controls the oscillator amplitude (loudness)

osc.start();

}

functiondraw(){

background(200);

}

functionmousePressed(){

env.triggerAttack();// note on - play attack to sustain

}

functionmouseReleased(){

env.triggerRelease();// play the release part - turn note off

}

Modular Synthesis and Vibrato

This next change illustrates the flexibility of sound synthesis systems based on modules. We want to add vibrato – a wavering frequency effect – to our tone. You might expect vibrato and other effects to be built-in or attributes, but instead, we create vibrato by using another oscillator to construct a low-frequency, sinusoidal “vibrato signal” and send that to the main tone oscillator (osc) where the vibrato signal modulates the frequency of the tone. You could make absurdly wide vibrato, change the vibrato signal from a smooth sinusoidal variation to an angular sawtooth variation, change the amount of vibrato with an envelope, or perform many other variations. Here’s some simple code implementing vibrato:

A tone triggered by the mouse with vibrato.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

varosc,env,vib;

functionsetup(){

createCanvas(400,400);

// osc is the main tone generator

osc=newp5.Oscillator();

osc.setType('sawtooth');

osc.freq(220);

// env controls the amplitude shape of osc

env=newp5.Env();

env.setADSR(0.01,1.2,0.3,0.2);

osc.amp(env);// envelope controls the oscillator amplitude (loudness)

// low-frequency oscillator for vibrato

vib=newp5.Oscillator();

vib.setType('sine');

vib.freq(6);

vib.amp(5);// controls depth of vibrato

vib.start();

osc.freq(vib);// modulate the osc frequency with vibrato

osc.start();

}

functiondraw(){

background(200);

}

functionmousePressed(){

env.triggerAttack();// note on - play attack to sustain

}

functionmouseReleased(){

env.triggerRelease();// play the release part - turn note off

}

Filters

We are building a classic “subtractive synthesis” tone, where the basic tone is rich in harmonics, and where a filter can remove harmonics to vary and shape the quality of the sound. This example passes the oscillator output through a filter controlled by another envelope, fenv. This envelope controls the frequencies present in the sound. Higher values correspond to more and higher frequencies passing through the filter.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

varosc,env,vib,filt,fenv;

functionsetup(){

createCanvas(400,400);

// osc is the main tone generator

osc=newp5.Oscillator();

osc.setType('sawtooth');

osc.freq(220);

// env controls the amplitude shape of osc

env=newp5.Env();

env.setADSR(0.01,1.2,0.3,0.2);

osc.amp(env);// envelope controls the oscillator amplitude (loudness)

// low-frequency oscillator for vibrato

vib=newp5.Oscillator();

vib.setType('sine');

vib.freq(6);

vib.amp(5);// controls depth of vibrato

vib.start();

osc.freq(vib);// modulate the osc frequency with vibrato

// a filter to change the timbre or spectral quality

filt=newp5.LowPass();// allows sound up to some frequency to pass through

osc.disconnect();// stop playing the oscillator directly

osc.connect(filt);// pass the osc output through the filter

// filter control envelope

fenv=newp5.Env();

fenv.setADSR(0.3,1.2,1000,0.2);

fenv.setRange(8000,400);

filt.freq(fenv);

osc.start();

}

functiondraw(){

background(200);

}

functionmousePressed(){

env.triggerAttack();// note on - play attack to sustain

fenv.triggerAttack();

}

functionmouseReleased(){

env.triggerRelease();// play the release part - turn note off

fenv.triggerRelease();

}

Sequencing

Playing one note is fun, but to play a melody or complex musical sequence, you need to turn notes on and off, change their frequencies (pitches), etc. p5.js is not particularly suited to this kind of scheduling because the model of p5.js is mainly one of “here’s the state NOW, draw the screen NOW.” You cannot say do this now and do that later, at least not without storing your plans as data and writing code to test the current time against the plan and peform actions at the right time. Even then, frame rates are not too predictable and while 60Hz is a reasonable frame rate for graphics, this makes for very coarse time steps as far as our ears are concerned.

Nevertheless, let’s make some note sequences. Here’s we’ll randomly choose to maybe play a note at every Nth frame.