1
+ /**
2
+ * Example: Fractal Music
3
+ * Generate a fractal pattern of music using L-Systems.
4
+ *
5
+ * We begin with an initial sequence of tokens, and use a programmer-
6
+ * defined set of rules to generate new tokens. Here, we apply the rules:
7
+ * A -> C, F, E
8
+ * C -> G, E
9
+ * E -> C, G
10
+ * F -> A, C
11
+ * G -> E, F, C
12
+ * to each token in existing sequences to generate new sequences. The
13
+ * tokens used here are plain note names from a fixed octave. Note durations
14
+ * are selected randomly from a palette of "nice" durations.
15
+ */
16
+
17
+ var rules = {
18
+ "A" :[ "C" , "F" , "E" ] ,
19
+ "C" :[ "G" , "E" ] ,
20
+ "E" :[ "C" , "G" ] ,
21
+ "F" :[ "A" , "C" ] ,
22
+ "G" :[ "E" , "F" , "C" ]
23
+ } ;
24
+ var seqIndex = 0 ;
25
+ var noteIndex = - 1 ;
26
+ var initSeq = [ "C" ] ;
27
+ var newTokens = [ ] ;
28
+ var sequences = [ initSeq , [ ] ] ;
29
+ var maxNumSequences = 8 ;
30
+ var maxSequenceLength = 30 ;
31
+
32
+ var generatingTokenColor ;
33
+ var newTokenColor ;
34
+ var fontSize = 20 ;
35
+ var highlightWidth = fontSize ;
36
+ var highlightHeight = fontSize ;
37
+
38
+ function setup ( ) {
39
+ createCanvas ( 720 , 400 ) ;
40
+ textAlign ( CENTER , CENTER ) ;
41
+ textFont ( 'monospace' ) ;
42
+ textSize ( fontSize ) ;
43
+ generatingTokenColor = color ( 200 , 250 , 255 ) ;
44
+ newTokenColor = color ( 240 ) ;
45
+
46
+ synth = new p5 . PolySynth ( ) ;
47
+ sloop = new p5 . SoundLoop ( soundLoop , 0.7 ) ;
48
+
49
+ playPauseButton = createButton ( 'Play/Pause' ) ;
50
+ playPauseButton . position ( width - 2 * playPauseButton . size ( ) . width , height - 2 * playPauseButton . size ( ) . height ) ;
51
+ playPauseButton . mousePressed ( togglePlayPause ) ;
52
+ stepButton = createButton ( "Step" ) ;
53
+ stepButton . position ( 2 * stepButton . size ( ) . width , height - 2 * stepButton . size ( ) . height ) ;
54
+ stepButton . mousePressed ( stepSoundLoop )
55
+ }
56
+
57
+ function soundLoop ( cycleStartTime ) {
58
+ noteIndex ++ ;
59
+ if ( noteIndex >= min ( sequences [ seqIndex ] . length , maxSequenceLength ) ) {
60
+ nextSequence ( ) ;
61
+ }
62
+ var token = sequences [ seqIndex ] [ noteIndex ] ;
63
+
64
+ var pitch = token + "4" ;
65
+ var velocity = 0.8 ;
66
+ var beatSeconds = 0.5 ; // Define 1 beat as half a second
67
+ var duration = random ( [ beatSeconds , beatSeconds / 2 , beatSeconds / 2 , beatSeconds / 4 ] ) ;
68
+ this . interval = duration ;
69
+ synth . play ( pitch , velocity , cycleStartTime , duration ) ;
70
+
71
+ newTokens = rules [ token ] ;
72
+ sequences [ seqIndex + 1 ] = sequences [ seqIndex + 1 ] . concat ( newTokens ) ;
73
+ // If the sequence overruns maxSequenceLength, truncate it and proceed to next sequence
74
+ if ( sequences [ seqIndex + 1 ] . length >= maxSequenceLength ) {
75
+ sequences [ seqIndex + 1 ] = sequences [ seqIndex + 1 ] . slice ( 0 , maxSequenceLength ) ;
76
+ nextSequence ( ) ;
77
+ }
78
+ }
79
+
80
+ function draw ( ) {
81
+ background ( 255 ) ;
82
+
83
+ highlightNote ( seqIndex , noteIndex , generatingTokenColor ) ;
84
+ for ( var i = 0 ; i < newTokens . length ; i ++ ) {
85
+ highlightNote ( seqIndex + 1 , sequences [ seqIndex + 1 ] . length - 1 - i , newTokenColor ) ;
86
+ }
87
+
88
+ textAlign ( CENTER , CENTER ) ;
89
+ noStroke ( ) ;
90
+ for ( var i = 0 ; i < sequences . length ; i ++ ) {
91
+ fill ( 255 - 195 * ( i + 1 ) / sequences . length ) ;
92
+ if ( i == sequences . length - 1 ) {
93
+ fill ( 0 , 150 , 255 ) ; // Generated tokens text
94
+ }
95
+ var seq = sequences [ i ] ;
96
+ var lineHeight = fontSize + 10 ;
97
+ text ( seq . join ( " " ) , width / 2 , height * 2 / 3 - lineHeight * ( sequences . length - i - 1 ) ) ;
98
+ }
99
+ }
100
+
101
+ function togglePlayPause ( ) {
102
+ // Play/pause
103
+ if ( sloop . isPlaying ) {
104
+ sloop . pause ( ) ;
105
+ } else {
106
+ sloop . maxIterations = Infinity ;
107
+ sloop . start ( ) ;
108
+ }
109
+ }
110
+
111
+ function stepSoundLoop ( ) {
112
+ sloop . stop ( ) ;
113
+ soundLoop ( 0 ) ;
114
+ }
115
+
116
+ function nextSequence ( ) {
117
+ noteIndex = 0 ;
118
+ seqIndex ++ ;
119
+ sequences . push ( [ ] ) ; // Add a new empty sequence
120
+ // If the number of sequences overruns maxNumSequences, remove oldest
121
+ if ( sequences . length > maxNumSequences ) {
122
+ seqIndex -- ;
123
+ sequences . shift ( ) ; // Removes first element from array
124
+ }
125
+ }
126
+
127
+ function highlightNote ( seqInd , noteInd , highlightColor ) {
128
+ if ( noteInd < 0 ) return ; // Skip if we haven't started
129
+ // X position of character
130
+ var seqWidth = textWidth ( sequences [ seqInd ] . join ( " " ) ) ;
131
+ var xOffset = width / 2 - seqWidth / 2 ;
132
+ var x = xOffset + seqWidth * noteInd / sequences [ seqInd ] . length ;
133
+ // Y position of character
134
+ var lineHeight = fontSize + 10 ;
135
+ var y = fontSize + height * 2 / 3 - lineHeight * ( sequences . length - seqInd - 0 ) ;
136
+
137
+ strokeWeight ( 1 ) ;
138
+ stroke ( 150 ) ;
139
+ fill ( highlightColor ) ;
140
+ rect ( x , y , highlightWidth , highlightHeight ) ;
141
+ }
0 commit comments