1
+ /**
2
+ * Example: Markov music
3
+ * Demonstrates the use of Markov chains to model musical sequences.
4
+ *
5
+ * The user adds notes to the Markov chain by playing notes ASDFGHJKL
6
+ * on the keyboard. Every note event (note on and note off) is registered
7
+ * as a new node in the graph, and the graph records all transitions
8
+ * between note events as edges along the graph.
9
+ *
10
+ * During playback mode, the algorithm simply traverses the graph randomly,
11
+ * playing notes based on the nodes upon which it traverses.
12
+ *
13
+ * The Markov chain (graph) is visualized as a standard network of nodes,
14
+ * with transitions represented by edges between nodes. The current/latest
15
+ * node as well as the edges of its previously recorded transitions are
16
+ * highlighted.
17
+ */
18
+
19
+ // Music
20
+ var synth ;
21
+ var velocity = 0.7 ; // From 0-1
22
+ var baseNote = 60 ;
23
+ var keyOrder = "ASDFGHJKL" ;
24
+ var keyScale = [ 0 , 2 , 4 , 5 , 7 , 9 , 11 , 12 , 14 ] ;
25
+ var keyStates = [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
26
+ // Markov Chain
27
+ var graph ;
28
+ var latestNodeId ;
29
+ // Playback SoundLoops
30
+ var sloop ;
31
+ var playing = false ;
32
+ var secondsPerTick = 0.1 ;
33
+ var prevEventMillis = 0 ;
34
+ var timeQuantizationStep = 100 ; // Quantize to 10 milliseconds
35
+ var maxDuration = 5000 ;
36
+ var longestDurationSoFar = timeQuantizationStep ;
37
+ // Colors
38
+ var DEFAULT_NODE_COLOR = [ 85 , 120 , 138 ] ;
39
+ var ACTIVE_NODE_COLOR = [ 205 , 0 , 100 ] ;
40
+
41
+ function setup ( ) {
42
+ createCanvas ( 720 , 400 ) ;
43
+ frameRate ( 15 ) ;
44
+ angleMode ( DEGREES ) ;
45
+
46
+ graph = new Graph ( ) ;
47
+ synth = new p5 . PolySynth ( ) ;
48
+ sloop = new p5 . SoundLoop ( soundLoop , 0.1 ) ;
49
+
50
+ playPauseButton = createButton ( "Play / Pause" ) ;
51
+ playPauseButton . position ( 20 , height - 40 ) ;
52
+ playPauseButton . mousePressed ( togglePlayPause ) ;
53
+
54
+ prevEventMillis = millis ( ) ;
55
+ }
56
+
57
+ function draw ( ) {
58
+ background ( 242 , 221 , 164 ) ;
59
+ fill ( 163 , 196 , 188 ) ;
60
+ rect ( 0 , 0 , width / 2 , height ) ;
61
+ // Draw edges
62
+ graph . drawEdges ( ) ;
63
+ // Draw nodes
64
+ for ( var i = 0 ; i < graph . nodes . length ; i ++ ) {
65
+ graph . nodes [ i ] . update ( ) ;
66
+ graph . nodes [ i ] . display ( ) ;
67
+ }
68
+ fill ( 0 ) ;
69
+ noStroke ( ) ;
70
+ textAlign ( CENTER , CENTER ) ;
71
+ // If there are no nodes, tell the users to play something
72
+ if ( graph . nodes . length == 0 ) {
73
+ text ( "Press any of ASDFGHJKL to add notes to the Markov chain." , width / 2 , 20 ) ;
74
+ }
75
+ // If we are at the end of the chain, tell the users
76
+ if ( latestNodeId != null && graph . edges [ latestNodeId ] . length == 0 ) {
77
+ text ( "Reached end of Markov chain. Play a new note to add to chain." , width / 2 , 20 ) ;
78
+ sloop . stop ( ) ;
79
+ synth . noteRelease ( ) ; // Release all notes
80
+ }
81
+
82
+ if ( sloop . isPlaying ) {
83
+ text ( "Generating from chain..." , width / 2 , height - 20 ) ;
84
+ } else {
85
+ text ( "PAUSED" , width / 2 , height - 20 ) ;
86
+ }
87
+ }
88
+
89
+ function soundLoop ( cycleStartTime ) {
90
+ // Play the sound of this node
91
+ var midiNoteNumber = graph . nodes [ latestNodeId ] . pitch ;
92
+ var freq = midiToFreq ( midiNoteNumber ) ;
93
+ var type = graph . nodes [ latestNodeId ] . type ;
94
+ if ( type == 1 ) {
95
+ synth . noteAttack ( freq , velocity , cycleStartTime ) ;
96
+ } else {
97
+ synth . noteRelease ( freq , cycleStartTime ) ;
98
+ }
99
+ // Transition to a random new node
100
+ if ( graph . edges [ latestNodeId ] . length ) {
101
+ latestNodeId = random ( graph . edges [ latestNodeId ] ) ;
102
+ }
103
+ // Wait for the timeFromPrevEvent of the new node
104
+ var timeSincePrevEvent = graph . nodes [ latestNodeId ] . timeSincePrevEvent / 1000 ; // Millis to seconds
105
+ this . interval = max ( timeSincePrevEvent , 0.01 ) ; // Cannot have interval of exactly 0
106
+ }
107
+
108
+ function keyPressed ( ) {
109
+ var keyIndex = keyOrder . indexOf ( key ) ;
110
+ // Check if valid note key pressed
111
+ if ( keyIndex >= 0 ) {
112
+ // Play synth
113
+ var midiNoteNumber = baseNote + keyScale [ keyIndex ] ; // 0-127; 60 is Middle C (C4)
114
+ var freq = midiToFreq ( midiNoteNumber ) ;
115
+ synth . noteAttack ( freq , velocity , 0 ) ;
116
+ // Update time
117
+ var timeSincePrevEvent = min ( millis ( ) - prevEventMillis , maxDuration ) ;
118
+ prevEventMillis = millis ( ) ;
119
+ var quantizedTimeSincePrevEvent = round ( timeSincePrevEvent / timeQuantizationStep ) * timeQuantizationStep ;
120
+ // Register node
121
+ graph . registerNewNode ( 1 , midiNoteNumber , quantizedTimeSincePrevEvent ) ;
122
+ // Activate key state
123
+ keyStates [ keyIndex ] = 1 ;
124
+ }
125
+ }
126
+
127
+ function keyReleased ( ) {
128
+ var keyIndex = keyOrder . indexOf ( key ) ;
129
+ // Check if valid note key pressed
130
+ if ( keyIndex >= 0 ) {
131
+ // Stop synth
132
+ midiNoteNumber = baseNote + keyScale [ keyIndex ] ; // 0-127; 60 is Middle C (C4)
133
+ freq = midiToFreq ( midiNoteNumber ) ;
134
+ synth . noteRelease ( freq , 0 ) ;
135
+ // Update time
136
+ var timeSincePrevEvent = min ( millis ( ) - prevEventMillis , maxDuration ) ;
137
+ prevEventMillis = millis ( ) ;
138
+ var quantizedTimeSincePrevEvent = round ( timeSincePrevEvent / timeQuantizationStep ) * timeQuantizationStep ;
139
+ // Register node
140
+ graph . registerNewNode ( 0 , midiNoteNumber , quantizedTimeSincePrevEvent ) ;
141
+ // Reset key state
142
+ keyStates [ keyIndex ] = 0 ;
143
+
144
+ timeSincePrevEvent = 0 ;
145
+ }
146
+ }
147
+
148
+ function togglePlayPause ( ) {
149
+ if ( sloop . isPlaying ) {
150
+ sloop . stop ( ) ;
151
+ synth . noteRelease ( ) ; // Release all notes
152
+ } else {
153
+ sloop . start ( ) ;
154
+ }
155
+ }
156
+
157
+ // Class for a single node
158
+ // characterized by ID, pitch and timeSincePrevEvent of the note it represents
159
+ function Node ( id , type , pitch , timeSincePrevEvent ) {
160
+ this . id = id ;
161
+ this . type = type ; // 1 (note on) or 0 (note off)
162
+ this . pitch = pitch ;
163
+ this . timeSincePrevEvent = timeSincePrevEvent ;
164
+ this . oscillateCounter = 0 ;
165
+
166
+ var x = map ( this . timeSincePrevEvent , 0 , maxDuration / 2 , 0 , width / 2 ) ;
167
+ if ( type === 1 ) {
168
+ x = width / 2 + x ;
169
+ } else {
170
+ x = width / 2 - x ;
171
+ }
172
+ var y = map ( this . pitch , baseNote , baseNote + max ( keyScale ) , height * 0.9 , height * 0.1 ) ;
173
+ this . center = createVector ( x , y ) ;
174
+ this . position = createVector ( x , y ) ;
175
+
176
+ this . color = ACTIVE_NODE_COLOR ;
177
+ this . diameter = map ( this . timeSincePrevEvent , 0 , maxDuration , 2 , height / 20 ) ;
178
+ }
179
+ Node . prototype . isSimilar = function ( node ) {
180
+ if ( this . type === node . type && this . pitch === node . pitch && this . duration === node . duration ) {
181
+ return true ;
182
+ } else {
183
+ return false ;
184
+ }
185
+ } ;
186
+ Node . prototype . update = function ( ) {
187
+ var yAmplitude = height / 150 ;
188
+ var xAmplitude = height / 300 ;
189
+ this . position . y = this . center . y + ( yAmplitude * sin ( this . oscillateCounter ) ) ;
190
+ this . position . x = this . center . x + ( xAmplitude * cos ( this . oscillateCounter ) ) ;
191
+ this . oscillateCounter = this . oscillateCounter + 6 ;
192
+ } ;
193
+ Node . prototype . display = function ( ) {
194
+ noStroke ( ) ;
195
+ var color = DEFAULT_NODE_COLOR ;
196
+ if ( this . id == latestNodeId ) {
197
+ // Highlight latest node
198
+ color = this . color ;
199
+ }
200
+ // Fill circle if note-on, stroke circle if note-off
201
+ if ( this . type == 1 ) {
202
+ noStroke ( ) ;
203
+ fill ( color [ 0 ] , color [ 1 ] , color [ 2 ] ) ;
204
+ } else {
205
+ noFill ( ) ;
206
+ strokeWeight ( 2 ) ;
207
+ stroke ( color [ 0 ] , color [ 1 ] , color [ 2 ] ) ;
208
+ }
209
+ ellipse ( this . position . x , this . position . y , this . diameter , this . diameter ) ;
210
+ } ;
211
+
212
+ // Graph data structure code adapted from
213
+ // http://blog.benoitvallon.com/data-structures-in-javascript/the-graph-data-structure/
214
+ function Graph ( ) {
215
+ this . nodes = [ ] ;
216
+ this . nodeIds = [ ] ;
217
+ this . edges = [ ] ;
218
+ this . numberOfEdges = 0 ;
219
+ }
220
+ Graph . prototype . findNode = function ( node ) {
221
+ for ( var i = 0 ; i < this . nodes . length ; i ++ ) {
222
+ if ( node . isSimilar ( this . nodes [ i ] ) ) {
223
+ return i ;
224
+ }
225
+ }
226
+ return - 1 ; // Not found
227
+ } ;
228
+ Graph . prototype . registerNewNode = function ( type , midiNoteNumber , timeSincePrevEvent ) {
229
+ var node = new Node ( 0 , type , midiNoteNumber , timeSincePrevEvent ) ;
230
+ var nodeId = graph . findNode ( node ) ;
231
+ if ( nodeId == - 1 ) { // If necessary, create the node
232
+ nodeId = this . nodes . length ;
233
+ this . addNode ( node ) ;
234
+ }
235
+ node . id = nodeId ;
236
+ if ( latestNodeId != null ) { // On initialization it will be null
237
+ // Add an edge from the previous node to this one
238
+ this . addEdge ( latestNodeId , nodeId ) ;
239
+ }
240
+ // Update the latest node ID
241
+ latestNodeId = nodeId ;
242
+ } ;
243
+ Graph . prototype . addNode = function ( node ) {
244
+ var nodeId = this . nodes . length ;
245
+ this . nodeIds . push ( nodeId ) ;
246
+ this . nodes . push ( node ) ;
247
+ this . edges [ nodeId ] = [ ] ;
248
+ } ;
249
+ Graph . prototype . addEdge = function ( nodeId1 , nodeId2 ) {
250
+ this . edges [ nodeId1 ] . push ( nodeId2 ) ;
251
+ this . numberOfEdges ++ ;
252
+ } ;
253
+ Graph . prototype . drawEdges = function ( ) {
254
+ // Draw all edges leading away from this node
255
+ strokeWeight ( 1 ) ;
256
+ for ( var i = 0 ; i < graph . edges . length ; i ++ ) {
257
+ var startNode = i ;
258
+ if ( startNode == latestNodeId ) { // Highlight the latest node's edges
259
+ stroke ( graph . nodes [ startNode ] . color [ 0 ] , graph . nodes [ startNode ] . color [ 1 ] , graph . nodes [ startNode ] . color [ 2 ] , 100 ) ;
260
+ } else {
261
+ stroke ( DEFAULT_NODE_COLOR ) ;
262
+ }
263
+ for ( var j = 0 ; j < graph . edges [ i ] . length ; j ++ ) {
264
+ var endNode = graph . edges [ i ] [ j ] ;
265
+ line ( graph . nodes [ startNode ] . position . x , graph . nodes [ startNode ] . position . y , graph . nodes [ endNode ] . position . x , graph . nodes [ endNode ] . position . y ) ;
266
+ }
267
+ }
268
+ } ;
0 commit comments