Skip to content

Commit 4962f4a

Browse files
committed
Change to note on/off and time-from-prev-event representation
1 parent da80dea commit 4962f4a

File tree

1 file changed

+62
-38
lines changed

1 file changed

+62
-38
lines changed

examples/markov_music/sketch.js

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
1+
// Music
12
var synth;
23
var velocity = 0.7; // From 0-1
34
var baseNote = 60;
45
var keyOrder = "ASDFGHJKL";
56
var keyScale = [0,2,4,5,7,9,11,12,14];
67
var keyStates = [0,0,0,0,0,0,0,0,0];
7-
8-
var sloop;
9-
var transition_table = {};
10-
var nodes = [];
8+
// Markov Chain
119
var graph;
1210
var latestNodeId;
11+
// Playback SoundLoops
12+
var sloop;
1313
var playing = false;
14-
var timeQuantization = 0.1;
14+
var mloop;
15+
var secondsPerTick = 0.1;
16+
var ticksSincePrevEvent = 0;
1517

1618
function setup() {
17-
createCanvas(700, 400);
18-
frameRate(20);
19-
20-
synth = new p5.PolySynth();
19+
createCanvas(window.innerWidth, window.innerHeight);
20+
frameRate(15);
2121

2222
graph = new Graph();
23-
23+
synth = new p5.PolySynth();
2424
sloop = new p5.SoundLoop(soundLoop, "16n");
25-
mloop = new p5.SoundLoop(metronomeLoop, timeQuantization);
25+
mloop = new p5.SoundLoop(metronomeLoop, secondsPerTick);
2626
mloop.start();
2727

2828
playPauseButton = createButton("Play / Pause");
29-
playPauseButton.position(20, 20);
29+
playPauseButton.position(20, height-40);
3030
playPauseButton.mousePressed(togglePlayPause);
3131
}
3232

@@ -40,40 +40,56 @@ function draw() {
4040
graph.nodes[i].update();
4141
graph.nodes[i].display();
4242
}
43+
fill(0);
44+
noStroke();
45+
textAlign(CENTER, CENTER);
46+
// If there are no nodes, tell the users to play something
47+
if (graph.nodes.length == 0) {
48+
text("Press any of ASDFGHJKL to add notes to the Markov chain.", width/2, height/8);
49+
}
50+
// If we are at the end of the chain, tell the users
51+
if (latestNodeId != null && graph.edges[latestNodeId].length == 0) {
52+
text("Reached end of Markov chain. Play a new note to add to chain.", width/2, height/2);
53+
}
4354
}
4455

4556
function soundLoop(cycleStartTime) {
46-
// Transition to a random new node
47-
latestNodeId = random(graph.edges[latestNodeId]);
4857
// Play the sound of this node
4958
var midiNoteNumber = graph.nodes[latestNodeId].pitch;
5059
var freq = midiToFreq(midiNoteNumber);
51-
var duration = graph.nodes[latestNodeId].duration * timeQuantization;
52-
synth.play(freq, velocity, cycleStartTime, duration);
60+
var type = graph.nodes[latestNodeId].type;
61+
if (type == 1) {
62+
synth.noteAttack(freq, velocity, cycleStartTime);
63+
} else {
64+
synth.noteRelease(freq, cycleStartTime);
65+
}
66+
// Transition to a random new node
67+
if (graph.edges[latestNodeId].length) {
68+
latestNodeId = random(graph.edges[latestNodeId]);
69+
}
70+
// Wait for the timeFromPrevEvent of the new node
71+
var duration = graph.nodes[latestNodeId].duration * secondsPerTick;
5372
this.interval = duration;
5473
}
5574

5675
function metronomeLoop(cycleStartTime) {
57-
// This loop measures the duration of each keypress
58-
// key-down durations are stored in the keyStates array
59-
for (var i=0; i<keyStates.length; i++) {
60-
if (keyStates[i] > 0) {
61-
keyStates[i] = keyStates[i] + 1;
62-
}
63-
}
76+
ticksSincePrevEvent = constrain(ticksSincePrevEvent + 1, 0, 30); // Limit the maximum ticks
6477
}
6578

6679
function keyPressed() {
6780
var keyIndex = keyOrder.indexOf(key);
6881
// Check if valid note key pressed
6982
if (keyIndex >= 0) {
70-
// Activate key state
71-
keyStates[keyIndex] = 1;
7283
// Play synth
7384
var midiNoteNumber = baseNote + keyScale[keyIndex]; // 0-127; 60 is Middle C (C4)
7485
var freq = midiToFreq(midiNoteNumber);
7586
synth.noteAttack(freq, velocity, 0);
87+
// Register node
88+
graph.registerNewNode(1, midiNoteNumber, ticksSincePrevEvent); //keyStates[keyIndex]);
89+
// Activate key state
90+
keyStates[keyIndex] = 1;
7691
}
92+
ticksSincePrevEvent = 0;
7793
}
7894

7995
function keyReleased() {
@@ -85,34 +101,36 @@ function keyReleased() {
85101
freq = midiToFreq(midiNoteNumber);
86102
synth.noteRelease(freq, 0);
87103
// Register node
88-
graph.registerNewNode(midiNoteNumber, keyStates[keyIndex]);
104+
graph.registerNewNode(0, midiNoteNumber, ticksSincePrevEvent);
89105
// Reset key state
90106
keyStates[keyIndex] = 0;
91107
}
108+
ticksSincePrevEvent = 0;
92109
}
93110

94111
function togglePlayPause() {
95112
if (sloop.isPlaying) {
96113
sloop.stop();
114+
synth.noteRelease(); // Release all notes
97115
} else {
98116
sloop.start();
99117
}
100118
}
101119

102-
103120
// Class for a single node
104121
// characterized by ID, pitch and duration of the note it represents
105-
function Node(id, pitch, duration) {
122+
function Node(id, type, pitch, duration) {
106123
this.id = id;
107124
this.color = [255, 0, 100];
108125
this.diameter = 10;
109126
this.position = createVector(width/2 + random(-100,100), height/2 + random(-100,100));
110127
this.velocity = createVector(random(-1,1), random(-1,1));
128+
this.type = type; // 1 (note on) or 0 (note off)
111129
this.pitch = pitch;
112130
this.duration = duration;
113131
}
114132
Node.prototype.isSimilar = function(node) {
115-
var squaredDist = (this.pitch - node.pitch)**2 + (this.duration - node.duration)**2;
133+
var squaredDist = (this.type - node.type)**2 + (this.pitch - node.pitch)**2 + (this.duration - node.duration)**2;
116134
if (squaredDist == 0) {
117135
return true;
118136
} else {
@@ -131,14 +149,21 @@ Node.prototype.update = function() {
131149
};
132150
Node.prototype.display = function() {
133151
noStroke();
134-
// Highlight latest node
135-
if (this.id == latestNodeId) {
136-
fill(this.color[0], this.color[1], this.color[2]);
137-
ellipse(this.position.x,this.position.y,this.diameter,this.diameter);
152+
var color = [200, 200, 200];
153+
if (this.id == latestNodeId) {
154+
// Highlight latest node
155+
color = this.color;
156+
}
157+
// Fill circle if note-on, stroke circle if note-off
158+
if (this.type == 1) {
159+
noStroke();
160+
fill(color[0], color[1], color[2]);
138161
} else {
139-
fill(200);
140-
ellipse(this.position.x,this.position.y,this.diameter,this.diameter);
162+
noFill();
163+
strokeWeight(2);
164+
stroke(color[0], color[1], color[2]);
141165
}
166+
ellipse(this.position.x,this.position.y,this.diameter,this.diameter);
142167
};
143168
Node.prototype.bounceOnBoundaries = function() {
144169
if (this.position.x >= width || this.position.x <= 0) {
@@ -151,7 +176,6 @@ Node.prototype.bounceOnBoundaries = function() {
151176
}
152177
};
153178

154-
155179
// Graph data structure code adapted from
156180
// http://blog.benoitvallon.com/data-structures-in-javascript/the-graph-data-structure/
157181
function Graph() {
@@ -168,8 +192,8 @@ Graph.prototype.findNode = function(node) {
168192
}
169193
return -1; // Not found
170194
};
171-
Graph.prototype.registerNewNode = function(midiNoteNumber, duration) {
172-
var node = new Node(0, midiNoteNumber, duration);
195+
Graph.prototype.registerNewNode = function(type, midiNoteNumber, duration) {
196+
var node = new Node(0, type, midiNoteNumber, duration);
173197
var nodeId = graph.findNode(node);
174198
if (nodeId == -1) { // If necessary, create the node
175199
nodeId = this.nodes.length;

0 commit comments

Comments
 (0)