@@ -29,32 +29,39 @@ var latestNodeId;
29
29
// Playback SoundLoops
30
30
var sloop ;
31
31
var playing = false ;
32
- var mloop ;
33
32
var secondsPerTick = 0.1 ;
34
- var ticksSincePrevEvent = 0 ;
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 ] ;
35
40
36
41
function setup ( ) {
37
- createCanvas ( window . innerWidth , window . innerHeight ) ;
42
+ createCanvas ( 720 , 400 ) ;
38
43
frameRate ( 15 ) ;
44
+ angleMode ( DEGREES ) ;
39
45
40
46
graph = new Graph ( ) ;
41
47
synth = new p5 . PolySynth ( ) ;
42
- sloop = new p5 . SoundLoop ( soundLoop , "16n" ) ;
43
- mloop = new p5 . SoundLoop ( metronomeLoop , secondsPerTick ) ;
44
- mloop . start ( ) ;
48
+ sloop = new p5 . SoundLoop ( soundLoop , 0.1 ) ;
45
49
46
50
playPauseButton = createButton ( "Play / Pause" ) ;
47
51
playPauseButton . position ( 20 , height - 40 ) ;
48
52
playPauseButton . mousePressed ( togglePlayPause ) ;
53
+
54
+ prevEventMillis = millis ( ) ;
49
55
}
50
56
51
57
function draw ( ) {
52
- background ( 255 ) ;
58
+ background ( 242 , 221 , 164 ) ;
59
+ fill ( 163 , 196 , 188 ) ;
60
+ rect ( 0 , 0 , width / 2 , height ) ;
53
61
// Draw edges
54
62
graph . drawEdges ( ) ;
55
63
// Draw nodes
56
64
for ( var i = 0 ; i < graph . nodes . length ; i ++ ) {
57
- graph . nodes [ i ] . bounceOnBoundaries ( ) ;
58
65
graph . nodes [ i ] . update ( ) ;
59
66
graph . nodes [ i ] . display ( ) ;
60
67
}
@@ -63,11 +70,19 @@ function draw() {
63
70
textAlign ( CENTER , CENTER ) ;
64
71
// If there are no nodes, tell the users to play something
65
72
if ( graph . nodes . length == 0 ) {
66
- text ( "Press any of ASDFGHJKL to add notes to the Markov chain." , width / 2 , height / 8 ) ;
73
+ text ( "Press any of ASDFGHJKL to add notes to the Markov chain." , width / 2 , 20 ) ;
67
74
}
68
75
// If we are at the end of the chain, tell the users
69
76
if ( latestNodeId != null && graph . edges [ latestNodeId ] . length == 0 ) {
70
- text ( "Reached end of Markov chain. Play a new note to add to chain." , width / 2 , height / 2 ) ;
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 ) ;
71
86
}
72
87
}
73
88
@@ -86,12 +101,8 @@ function soundLoop(cycleStartTime) {
86
101
latestNodeId = random ( graph . edges [ latestNodeId ] ) ;
87
102
}
88
103
// Wait for the timeFromPrevEvent of the new node
89
- var duration = graph . nodes [ latestNodeId ] . duration * secondsPerTick ;
90
- this . interval = duration ;
91
- }
92
-
93
- function metronomeLoop ( cycleStartTime ) {
94
- ticksSincePrevEvent = constrain ( ticksSincePrevEvent + 1 , 0 , 30 ) ; // Limit the maximum ticks
104
+ var timeSincePrevEvent = graph . nodes [ latestNodeId ] . timeSincePrevEvent / 1000 ; // Millis to seconds
105
+ this . interval = max ( timeSincePrevEvent , 0.01 ) ; // Cannot have interval of exactly 0
95
106
}
96
107
97
108
function keyPressed ( ) {
@@ -102,12 +113,15 @@ function keyPressed() {
102
113
var midiNoteNumber = baseNote + keyScale [ keyIndex ] ; // 0-127; 60 is Middle C (C4)
103
114
var freq = midiToFreq ( midiNoteNumber ) ;
104
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 ;
105
120
// Register node
106
- graph . registerNewNode ( 1 , midiNoteNumber , ticksSincePrevEvent ) ; //keyStates[keyIndex] );
121
+ graph . registerNewNode ( 1 , midiNoteNumber , quantizedTimeSincePrevEvent ) ;
107
122
// Activate key state
108
123
keyStates [ keyIndex ] = 1 ;
109
124
}
110
- ticksSincePrevEvent = 0 ;
111
125
}
112
126
113
127
function keyReleased ( ) {
@@ -118,12 +132,17 @@ function keyReleased() {
118
132
midiNoteNumber = baseNote + keyScale [ keyIndex ] ; // 0-127; 60 is Middle C (C4)
119
133
freq = midiToFreq ( midiNoteNumber ) ;
120
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 ;
121
139
// Register node
122
- graph . registerNewNode ( 0 , midiNoteNumber , ticksSincePrevEvent ) ;
140
+ graph . registerNewNode ( 0 , midiNoteNumber , quantizedTimeSincePrevEvent ) ;
123
141
// Reset key state
124
142
keyStates [ keyIndex ] = 0 ;
143
+
144
+ timeSincePrevEvent = 0 ;
125
145
}
126
- ticksSincePrevEvent = 0 ;
127
146
}
128
147
129
148
function togglePlayPause ( ) {
@@ -136,38 +155,44 @@ function togglePlayPause() {
136
155
}
137
156
138
157
// Class for a single node
139
- // characterized by ID, pitch and duration of the note it represents
140
- function Node ( id , type , pitch , duration ) {
158
+ // characterized by ID, pitch and timeSincePrevEvent of the note it represents
159
+ function Node ( id , type , pitch , timeSincePrevEvent ) {
141
160
this . id = id ;
142
- this . color = [ 255 , 0 , 100 ] ;
143
- this . diameter = 10 ;
144
- this . position = createVector ( width / 2 + random ( - 100 , 100 ) , height / 2 + random ( - 100 , 100 ) ) ;
145
- this . velocity = createVector ( random ( - 1 , 1 ) , random ( - 1 , 1 ) ) ;
146
161
this . type = type ; // 1 (note on) or 0 (note off)
147
162
this . pitch = pitch ;
148
- this . duration = duration ;
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 ) ;
149
178
}
150
179
Node . prototype . isSimilar = function ( node ) {
151
- var squaredDist = ( this . type - node . type ) ** 2 + ( this . pitch - node . pitch ) ** 2 + ( this . duration - node . duration ) ** 2 ;
152
- if ( squaredDist == 0 ) {
180
+ if ( this . type === node . type && this . pitch === node . pitch && this . duration === node . duration ) {
153
181
return true ;
154
182
} else {
155
183
return false ;
156
184
}
157
185
} ;
158
- Node . prototype . applyForce = function ( force ) {
159
- var f = p5 . Vector . div ( force , this . diameter ) ;
160
- this . acceleration . add ( f ) ;
161
- } ;
162
186
Node . prototype . update = function ( ) {
163
- // Velocity changes according to acceleration
164
- this . velocity . add ( this . acceleration ) ;
165
- // position changes by velocity
166
- this . position . add ( this . velocity ) ;
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 ;
167
192
} ;
168
193
Node . prototype . display = function ( ) {
169
194
noStroke ( ) ;
170
- var color = [ 200 , 200 , 200 ] ;
195
+ var color = DEFAULT_NODE_COLOR ;
171
196
if ( this . id == latestNodeId ) {
172
197
// Highlight latest node
173
198
color = this . color ;
@@ -181,17 +206,7 @@ Node.prototype.display = function() {
181
206
strokeWeight ( 2 ) ;
182
207
stroke ( color [ 0 ] , color [ 1 ] , color [ 2 ] ) ;
183
208
}
184
- ellipse ( this . position . x , this . position . y , this . diameter , this . diameter ) ;
185
- } ;
186
- Node . prototype . bounceOnBoundaries = function ( ) {
187
- if ( this . position . x >= width || this . position . x <= 0 ) {
188
- this . velocity . x *= - 1 ;
189
- this . position . x = constrain ( this . position . x , 0 , width ) ;
190
- }
191
- if ( this . position . y >= height || this . position . y <= 0 ) {
192
- this . velocity . y *= - 1 ;
193
- this . position . y = constrain ( this . position . y , 0 , height ) ;
194
- }
209
+ ellipse ( this . position . x , this . position . y , this . diameter , this . diameter ) ;
195
210
} ;
196
211
197
212
// Graph data structure code adapted from
@@ -210,8 +225,8 @@ Graph.prototype.findNode = function(node) {
210
225
}
211
226
return - 1 ; // Not found
212
227
} ;
213
- Graph . prototype . registerNewNode = function ( type , midiNoteNumber , duration ) {
214
- var node = new Node ( 0 , type , midiNoteNumber , duration ) ;
228
+ Graph . prototype . registerNewNode = function ( type , midiNoteNumber , timeSincePrevEvent ) {
229
+ var node = new Node ( 0 , type , midiNoteNumber , timeSincePrevEvent ) ;
215
230
var nodeId = graph . findNode ( node ) ;
216
231
if ( nodeId == - 1 ) { // If necessary, create the node
217
232
nodeId = this . nodes . length ;
@@ -243,7 +258,7 @@ Graph.prototype.drawEdges = function() {
243
258
if ( startNode == latestNodeId ) { // Highlight the latest node's edges
244
259
stroke ( graph . nodes [ startNode ] . color [ 0 ] , graph . nodes [ startNode ] . color [ 1 ] , graph . nodes [ startNode ] . color [ 2 ] , 100 ) ;
245
260
} else {
246
- stroke ( 200 , 100 ) ;
261
+ stroke ( DEFAULT_NODE_COLOR ) ;
247
262
}
248
263
for ( var j = 0 ; j < graph . edges [ i ] . length ; j ++ ) {
249
264
var endNode = graph . edges [ i ] [ j ] ;
0 commit comments