1
+ // Music
1
2
var synth ;
2
3
var velocity = 0.7 ; // From 0-1
3
4
var baseNote = 60 ;
4
5
var keyOrder = "ASDFGHJKL" ;
5
6
var keyScale = [ 0 , 2 , 4 , 5 , 7 , 9 , 11 , 12 , 14 ] ;
6
7
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
11
9
var graph ;
12
10
var latestNodeId ;
11
+ // Playback SoundLoops
12
+ var sloop ;
13
13
var playing = false ;
14
- var timeQuantization = 0.1 ;
14
+ var mloop ;
15
+ var secondsPerTick = 0.1 ;
16
+ var ticksSincePrevEvent = 0 ;
15
17
16
18
function setup ( ) {
17
- createCanvas ( 700 , 400 ) ;
18
- frameRate ( 20 ) ;
19
-
20
- synth = new p5 . PolySynth ( ) ;
19
+ createCanvas ( window . innerWidth , window . innerHeight ) ;
20
+ frameRate ( 15 ) ;
21
21
22
22
graph = new Graph ( ) ;
23
-
23
+ synth = new p5 . PolySynth ( ) ;
24
24
sloop = new p5 . SoundLoop ( soundLoop , "16n" ) ;
25
- mloop = new p5 . SoundLoop ( metronomeLoop , timeQuantization ) ;
25
+ mloop = new p5 . SoundLoop ( metronomeLoop , secondsPerTick ) ;
26
26
mloop . start ( ) ;
27
27
28
28
playPauseButton = createButton ( "Play / Pause" ) ;
29
- playPauseButton . position ( 20 , 20 ) ;
29
+ playPauseButton . position ( 20 , height - 40 ) ;
30
30
playPauseButton . mousePressed ( togglePlayPause ) ;
31
31
}
32
32
@@ -40,40 +40,56 @@ function draw() {
40
40
graph . nodes [ i ] . update ( ) ;
41
41
graph . nodes [ i ] . display ( ) ;
42
42
}
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
+ }
43
54
}
44
55
45
56
function soundLoop ( cycleStartTime ) {
46
- // Transition to a random new node
47
- latestNodeId = random ( graph . edges [ latestNodeId ] ) ;
48
57
// Play the sound of this node
49
58
var midiNoteNumber = graph . nodes [ latestNodeId ] . pitch ;
50
59
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 ;
53
72
this . interval = duration ;
54
73
}
55
74
56
75
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
64
77
}
65
78
66
79
function keyPressed ( ) {
67
80
var keyIndex = keyOrder . indexOf ( key ) ;
68
81
// Check if valid note key pressed
69
82
if ( keyIndex >= 0 ) {
70
- // Activate key state
71
- keyStates [ keyIndex ] = 1 ;
72
83
// Play synth
73
84
var midiNoteNumber = baseNote + keyScale [ keyIndex ] ; // 0-127; 60 is Middle C (C4)
74
85
var freq = midiToFreq ( midiNoteNumber ) ;
75
86
synth . noteAttack ( freq , velocity , 0 ) ;
87
+ // Register node
88
+ graph . registerNewNode ( 1 , midiNoteNumber , ticksSincePrevEvent ) ; //keyStates[keyIndex]);
89
+ // Activate key state
90
+ keyStates [ keyIndex ] = 1 ;
76
91
}
92
+ ticksSincePrevEvent = 0 ;
77
93
}
78
94
79
95
function keyReleased ( ) {
@@ -85,34 +101,36 @@ function keyReleased() {
85
101
freq = midiToFreq ( midiNoteNumber ) ;
86
102
synth . noteRelease ( freq , 0 ) ;
87
103
// Register node
88
- graph . registerNewNode ( midiNoteNumber , keyStates [ keyIndex ] ) ;
104
+ graph . registerNewNode ( 0 , midiNoteNumber , ticksSincePrevEvent ) ;
89
105
// Reset key state
90
106
keyStates [ keyIndex ] = 0 ;
91
107
}
108
+ ticksSincePrevEvent = 0 ;
92
109
}
93
110
94
111
function togglePlayPause ( ) {
95
112
if ( sloop . isPlaying ) {
96
113
sloop . stop ( ) ;
114
+ synth . noteRelease ( ) ; // Release all notes
97
115
} else {
98
116
sloop . start ( ) ;
99
117
}
100
118
}
101
119
102
-
103
120
// Class for a single node
104
121
// characterized by ID, pitch and duration of the note it represents
105
- function Node ( id , pitch , duration ) {
122
+ function Node ( id , type , pitch , duration ) {
106
123
this . id = id ;
107
124
this . color = [ 255 , 0 , 100 ] ;
108
125
this . diameter = 10 ;
109
126
this . position = createVector ( width / 2 + random ( - 100 , 100 ) , height / 2 + random ( - 100 , 100 ) ) ;
110
127
this . velocity = createVector ( random ( - 1 , 1 ) , random ( - 1 , 1 ) ) ;
128
+ this . type = type ; // 1 (note on) or 0 (note off)
111
129
this . pitch = pitch ;
112
130
this . duration = duration ;
113
131
}
114
132
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 ;
116
134
if ( squaredDist == 0 ) {
117
135
return true ;
118
136
} else {
@@ -131,14 +149,21 @@ Node.prototype.update = function() {
131
149
} ;
132
150
Node . prototype . display = function ( ) {
133
151
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 ] ) ;
138
161
} 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 ] ) ;
141
165
}
166
+ ellipse ( this . position . x , this . position . y , this . diameter , this . diameter ) ;
142
167
} ;
143
168
Node . prototype . bounceOnBoundaries = function ( ) {
144
169
if ( this . position . x >= width || this . position . x <= 0 ) {
@@ -151,7 +176,6 @@ Node.prototype.bounceOnBoundaries = function() {
151
176
}
152
177
} ;
153
178
154
-
155
179
// Graph data structure code adapted from
156
180
// http://blog.benoitvallon.com/data-structures-in-javascript/the-graph-data-structure/
157
181
function Graph ( ) {
@@ -168,8 +192,8 @@ Graph.prototype.findNode = function(node) {
168
192
}
169
193
return - 1 ; // Not found
170
194
} ;
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 ) ;
173
197
var nodeId = graph . findNode ( node ) ;
174
198
if ( nodeId == - 1 ) { // If necessary, create the node
175
199
nodeId = this . nodes . length ;
0 commit comments