Skip to content

Commit 45431f9

Browse files
JunSherntherewasaguy
authored andcommitted
Examples for Timing and Visualization by @JunShern (#302)
* Add visualize_pentatonic example for soundloop + note-by-note visualization * Swap play/pause button for click-to-start text on canvas * Add visualize_pentatonic example for soundloop + note-by-note visualization * Swap play/pause button for click-to-start text on canvas * Remove custom ADSR, it sounds fine using new voice defaults * Add particle system to visualize_pentatonic example * Add virtual piano example * Remove extra code not necessary for examples * Separate freqToMidi conversion into own variable * monosynth: remove unused filter, patch velocity * monosynth: remove unused _isOn * update master limiter - sharp knee * Simplify to minimal keypress example! Much cleaner * Add array_of_notes example for static playback * Re-run gh-pages/generator.js to update examples page * Add visualize_pentatonic example for soundloop + note-by-note visualization * Swap play/pause button for click-to-start text on canvas * Remove custom ADSR, it sounds fine using new voice defaults * Add particle system to visualize_pentatonic example * Add virtual piano example * Remove extra code not necessary for examples * Separate freqToMidi conversion into own variable * Simplify to minimal keypress example! Much cleaner * Add array_of_notes example for static playback * Re-run gh-pages/generator.js to update examples page * Update velocities after rebase (master changed monosynth volumes) * Add soundloop version for array_of_notes playback. * Fix bug for BPM information on array_of_notes_soundloop example * Fix comments for soundloop array of notes example * Add new grid_sequencer example, not yet finished * Add working grid sequencer example, not yet polished * Add polish to grid sequencer, should be good to go! * Change sequencer scale to pentatonic scale to make it sound better * Fix style-formatting and change from relative duration to seconds * Add touch support, change color and auto-format style * Format code for static song playback * Change from note+duration representation to note events representation * Change UI layout for grid sequencer, format code * Add comments to all examples
1 parent 199ee91 commit 45431f9

File tree

11 files changed

+572
-0
lines changed

11 files changed

+572
-0
lines changed

examples/array_of_notes/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<head>
2+
<script language="javascript" type="text/javascript" src="../../lib/p5.js"></script>
3+
4+
<script language="javascript" type="text/javascript" src="../../lib/addons/p5.dom.js"></script>
5+
6+
<script language="javascript" type="text/javascript" src="../../lib/p5.sound.js"></script>
7+
8+
<script language="javascript" type="text/javascript" src="sketch.js"></script>
9+
10+
</head>

examples/array_of_notes/sketch.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Basic example of playing back a static array of notes.
3+
*
4+
* Note that this implementation does not allow for starting/stopping
5+
* or dynamically modifying playback once it has started. For a more
6+
* versatile example of playback, see the SoundLoop version.
7+
*/
8+
9+
var synth;
10+
var songStarted = false;
11+
var song = [
12+
// Note pitch, velocity (between 0-1), start time (s), note duration (s)
13+
{ pitch: 'E4', velocity: 1, time: 0, duration: 1 },
14+
{ pitch: 'D4', velocity: 1, time: 1, duration: 1 },
15+
{ pitch: 'C4', velocity: 1, time: 2, duration: 1 },
16+
{ pitch: 'D4', velocity: 1, time: 3, duration: 1 },
17+
{ pitch: 'E4', velocity: 1, time: 4, duration: 1 },
18+
{ pitch: 'E4', velocity: 1, time: 5, duration: 1 },
19+
{ pitch: 'E4', velocity: 1, time: 6, duration: 1 },
20+
// Rest indicated by offset in start time
21+
{ pitch: 'D4', velocity: 1, time: 8, duration: 1 },
22+
{ pitch: 'D4', velocity: 1, time: 9, duration: 1 },
23+
{ pitch: 'E4', velocity: 1, time: 10, duration: 1 },
24+
{ pitch: 'D4', velocity: 1, time: 11, duration: 1 },
25+
// Chord indicated by simultaneous note start times
26+
{ pitch: 'C4', velocity: 1, time: 12, duration: 2 },
27+
{ pitch: 'E4', velocity: 1, time: 12, duration: 2 },
28+
{ pitch: 'G4', velocity: 1, time: 12, duration: 2 },
29+
];
30+
31+
function setup() {
32+
textAlign(CENTER, CENTER);
33+
synth = new p5.PolySynth();
34+
}
35+
36+
function draw() {
37+
background(255);
38+
if (songStarted) {
39+
text('song started', width / 2, height / 2);
40+
} else {
41+
text('click to play song', width / 2, height / 2);
42+
}
43+
}
44+
45+
function touchStarted() {
46+
if (!songStarted) { // Only play once
47+
for (var i = 0; i < song.length; i++) {
48+
note = song[i];
49+
synth.play(note.pitch, note.velocity, note.time, note.duration);
50+
}
51+
songStarted = true;
52+
}
53+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<head>
2+
<script language="javascript" type="text/javascript" src="../../lib/p5.js"></script>
3+
4+
<script language="javascript" type="text/javascript" src="../../lib/addons/p5.dom.js"></script>
5+
6+
<script language="javascript" type="text/javascript" src="../../lib/p5.sound.js"></script>
7+
8+
<script language="javascript" type="text/javascript" src="sketch.js"></script>
9+
10+
</head>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Using the SoundLoop to play back an array of notes with dynamic control.
3+
*
4+
* The array of notes uses a note-on and note-off event representation;
5+
* this separation is necessary so that we can have polyphonic sound
6+
* and dynamic starting and stopping of notes during playback.
7+
*/
8+
9+
var synth;
10+
var sloop;
11+
var eventIndex = 0;
12+
var beatsPerMinute = 120;
13+
var secondsPerBeat;
14+
var song = [
15+
// event pitch, velocity (between 0-1), time since previous event (beats), type (1:ON or 0:OFF)
16+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
17+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
18+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
19+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
20+
{ pitch: 'C4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
21+
{ pitch: 'C4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
22+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
23+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
24+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
25+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
26+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
27+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
28+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
29+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
30+
// Rest
31+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 1, type: 1 },
32+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
33+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
34+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
35+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
36+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
37+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
38+
{ pitch: 'D4', velocity: 1, timeSincePrevEvent: 1, type: 0 },
39+
// Chord indicated by multiple notes being ON at the same time
40+
{ pitch: 'C4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
41+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
42+
{ pitch: 'G4', velocity: 1, timeSincePrevEvent: 0, type: 1 },
43+
{ pitch: 'C4', velocity: 1, timeSincePrevEvent: 2, type: 0 },
44+
{ pitch: 'E4', velocity: 1, timeSincePrevEvent: 2, type: 0 },
45+
{ pitch: 'G4', velocity: 1, timeSincePrevEvent: 2, type: 0 },
46+
];
47+
48+
function setup() {
49+
createCanvas(720, 400);
50+
synth = new p5.PolySynth();
51+
sloop = new p5.SoundLoop(soundLoop, 0.1); // Interval doesn't matter; it changes in each loop iteration
52+
}
53+
54+
function soundLoop(cycleStartTime) {
55+
var event = song[eventIndex];
56+
if (event.type == 1) {
57+
synth.noteAttack(event.pitch, event.velocity, cycleStartTime);
58+
} else {
59+
synth.noteRelease(event.pitch, cycleStartTime);
60+
}
61+
// Prepare for next event
62+
eventIndex++;
63+
if (eventIndex >= song.length) {
64+
this.stop(cycleStartTime);
65+
} else {
66+
var nextEvent = song[eventIndex];
67+
// This cycle will last for the time since previous event of the next event
68+
secondsPerBeat = 60 / beatsPerMinute;
69+
70+
var duration = nextEvent.timeSincePrevEvent * secondsPerBeat;
71+
this.interval = max(duration, 0.01); // Cannot have interval of exactly 0
72+
}
73+
}
74+
75+
function draw() {
76+
background(255, 220, 90);
77+
// Change beats-per-minute based on mouse speed
78+
beatsPerMinute = map(mouseX, 0, width, 60, 200);
79+
line(mouseX, 0, mouseX, height);
80+
textAlign(LEFT, BOTTOM);
81+
text("BPM: " + nfc(beatsPerMinute, 0), mouseX + 5, height - 10);
82+
83+
textAlign(CENTER, CENTER);
84+
if (sloop.isPlaying) {
85+
text('click to stop song', width / 2, height / 2);
86+
} else {
87+
text('click to play song', width / 2, height / 2);
88+
}
89+
}
90+
91+
function touchStarted() {
92+
if (sloop.isPlaying) {
93+
sloop.stop();
94+
synth.noteRelease(); // Release all notes
95+
} else {
96+
// Reset counters
97+
eventIndex = 0;
98+
sloop.start();
99+
}
100+
}

examples/grid_sequencer/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<head>
2+
<script language="javascript" type="text/javascript" src="../../lib/p5.js"></script>
3+
4+
<script language="javascript" type="text/javascript" src="../../lib/addons/p5.dom.js"></script>
5+
6+
<script language="javascript" type="text/javascript" src="../../lib/p5.sound.js"></script>
7+
8+
<script language="javascript" type="text/javascript" src="sketch.js"></script>
9+
10+
</head>

examples/grid_sequencer/sketch.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* Example of using the p5.SoundLoop in a dynamic user-controlled
3+
* manner: the grid sequencer.
4+
*
5+
* Users can select what tune the sequencer plays by clicking to
6+
* activate/deactive notes, start and pause playback, and control
7+
* playback tempo on-the-fly.
8+
*/
9+
10+
var sloop;
11+
var bpm = 140; // 140 beats per minute
12+
13+
var numTimeSteps = 16;
14+
var timeStepCounter = 0;
15+
var pitches = [57, 60, 62, 64, 67, 69, 72, 74, 76, 79, 81, 84]; // A minor pentatonic scale
16+
17+
var cells = [];
18+
var cellWidth, cellHeight;
19+
var controlPanelHeight;
20+
21+
function setup() {
22+
createCanvas(720, 400);
23+
frameRate(10);
24+
controlPanelHeight = height / pitches.length;
25+
26+
// Prepare cells
27+
cellWidth = width / numTimeSteps;
28+
cellHeight = (height - controlPanelHeight) / pitches.length;
29+
for (var i = 0; i < numTimeSteps; i++) {
30+
for (var j = 0; j < pitches.length; j++) {
31+
var x = i * cellWidth;
32+
var y = controlPanelHeight + j * cellHeight;
33+
var pitch = pitches[pitches.length - j - 1]; // Pitches go from bottom to top
34+
var newCell = new Cell(createVector(x, y), pitch);
35+
cells.push(newCell);
36+
}
37+
}
38+
39+
// Create a synth to make sound with
40+
synth = new p5.PolySynth();
41+
42+
// Create SoundLoop with 8th-note-long loop interval
43+
sloop = new p5.SoundLoop(soundLoop, "8n");
44+
sloop.bpm = bpm;
45+
46+
// UI
47+
playPauseButton = createButton('PLAY/PAUSE');
48+
playPauseButton.mousePressed(togglePlayPause);
49+
playPauseButton.position(0, 0);
50+
playPauseButton.size(width / 4, controlPanelHeight);
51+
52+
tempoSlider = createSlider(30, 300, bpm);
53+
tempoSlider.position(width / 4, 0);
54+
tempoSlider.size(width / 4, controlPanelHeight);
55+
tempoText = createP("BPM: " + bpm);
56+
tempoText.position(width / 2, 0);
57+
tempoText.size(width / 4, controlPanelHeight);
58+
59+
clearButton = createButton('CLEAR ALL');
60+
clearButton.mousePressed(clearAll);
61+
clearButton.position(width * 3 / 4, 0);
62+
clearButton.size(width / 4, controlPanelHeight);
63+
}
64+
65+
function soundLoop(cycleStartTime) {
66+
for (var i = 0; i < cells.length; i++) {
67+
if (floor(i / pitches.length) == timeStepCounter) {
68+
cells[i].active = true;
69+
if (cells[i].enabled) {
70+
// Play sound
71+
var velocity = 1; // Between 0-1
72+
var quaverSeconds = this._convertNotation('8n'); // 8th note = quaver duration
73+
var freq = midiToFreq(cells[i].pitch);
74+
synth.play(freq, velocity, cycleStartTime, quaverSeconds);
75+
}
76+
} else {
77+
cells[i].active = false;
78+
}
79+
}
80+
this.bpm = bpm;
81+
timeStepCounter = (timeStepCounter + 1) % numTimeSteps;
82+
}
83+
84+
function draw() {
85+
background(255);
86+
for (var i = 0; i < cells.length; i++) {
87+
cells[i].checkIfHovered();
88+
cells[i].display();
89+
}
90+
bpm = tempoSlider.value();
91+
tempoText.html("BPM: " + bpm);
92+
}
93+
94+
function mouseClicked() {
95+
for (var i = 0; i < cells.length; i++) {
96+
if (cells[i].hovered) {
97+
cells[i].enabled = !cells[i].enabled;
98+
}
99+
}
100+
}
101+
102+
function togglePlayPause() {
103+
if (sloop.isPlaying) {
104+
sloop.pause();
105+
} else {
106+
sloop.start();
107+
}
108+
}
109+
110+
function clearAll() {
111+
for (var i = 0; i < cells.length; i++) {
112+
cells[i].enabled = false;
113+
}
114+
}
115+
116+
117+
var Cell = function (position, pitch) {
118+
// Sound
119+
this.pitch = pitch;
120+
// Appearance
121+
this.padding = 2;
122+
this.position = position.copy();
123+
this.width = cellWidth - 2 * this.padding;
124+
this.height = cellHeight - 2 * this.padding;
125+
this.defaultColor = [190, 240, 255];
126+
// Mouse hover
127+
this.hovered = false;
128+
this.hoverColor = [230, 255, 255];
129+
// Enabled when clicked
130+
this.enabled = false;
131+
var varyingColorVal = 22 * (this.pitch % pitches.length);
132+
this.enabledColor = [20 + varyingColorVal, 255 - varyingColorVal, 255];
133+
// Active when soundloop plays the cell
134+
this.active = false;
135+
this.activeColor = [230, 255, 255];
136+
}
137+
138+
Cell.prototype.display = function () {
139+
noStroke();
140+
if (this.enabled) {
141+
fill(this.enabledColor[0], this.enabledColor[1], this.enabledColor[2]);
142+
} else if (this.hovered) {
143+
fill(this.hoverColor[0], this.hoverColor[1], this.hoverColor[2]);
144+
} else if (this.active) {
145+
fill(this.activeColor[0], this.activeColor[1], this.activeColor[2]);
146+
} else {
147+
fill(this.defaultColor[0], this.defaultColor[1], this.defaultColor[2]);
148+
}
149+
rect(this.position.x + this.padding, this.position.y + this.padding, this.width, this.height);
150+
}
151+
152+
Cell.prototype.checkIfHovered = function () {
153+
var xMin = this.position.x + this.padding;
154+
var xMax = xMin + this.width;
155+
var yMin = this.position.y + this.padding;
156+
var yMax = yMin + this.height;
157+
if ((mouseX > xMin && mouseX < xMax) && (mouseY > yMin && mouseY < yMax)) {
158+
this.hovered = true;
159+
} else {
160+
this.hovered = false;
161+
}
162+
}

examples/virtual_piano/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<head>
2+
<script language="javascript" type="text/javascript" src="../../lib/p5.js"></script>
3+
4+
<script language="javascript" type="text/javascript" src="../../lib/addons/p5.dom.js"></script>
5+
6+
<script language="javascript" type="text/javascript" src="../../lib/p5.sound.js"></script>
7+
8+
<script language="javascript" type="text/javascript" src="sketch.js"></script>
9+
10+
</head>

0 commit comments

Comments
 (0)