shuvit 2 years ago
commit
13120681e3
19 changed files with 4979 additions and 0 deletions
  1. 48
    0
      .gitignore
  2. 5
    0
      config.txt
  3. 95
    0
      lib/FSM.py
  4. 1301
    0
      lib/StatesProg.py
  5. 0
    0
      lib/__init__.py
  6. 2484
    0
      lib/configobj.py
  7. 24
    0
      lib/observer.py
  8. 473
    0
      main.py
  9. 21
    0
      scripts/blinka_test.py
  10. 48
    0
      scripts/display_test.py
  11. 21
    0
      scripts/map.py
  12. 12
    0
      scripts/metro.py
  13. 396
    0
      scripts/test.py
  14. 3
    0
      scripts/timer.py
  15. 14
    0
      themes/dark.thm
  16. 14
    0
      themes/default.thm
  17. 7
    0
      user/songs/_blank.sng
  18. 7
    0
      user/songs/default.sng
  19. 6
    0
      user/songs/r100.sng

+ 48
- 0
.gitignore View File

@@ -0,0 +1,48 @@
1
+# Compiled source #
2
+###################
3
+*.com
4
+*.class
5
+*.dll
6
+*.exe
7
+*.o
8
+*.so
9
+
10
+# Packages #
11
+############
12
+# it's better to unpack these files and commit the raw source
13
+# git has its own built in compression methods
14
+*.7z
15
+*.dmg
16
+*.gz
17
+*.iso
18
+*.jar
19
+*.rar
20
+*.tar
21
+*.zip
22
+
23
+# Logs and databases #
24
+######################
25
+*.log
26
+*.sql
27
+*.sqlite
28
+
29
+# OS generated files #
30
+######################
31
+.DS_Store
32
+.DS_Store?
33
+._*
34
+.Spotlight-V100
35
+.Trashes
36
+ehthumbs.db
37
+Thumbs.db
38
+
39
+# User               #
40
+######################
41
+*.wav
42
+*.mp3
43
+*.WAV
44
+*.MP3
45
+*__pycache__/
46
+lib/__pycache__/
47
+lib/*.pyc
48
+

+ 5
- 0
config.txt View File

@@ -0,0 +1,5 @@
1
+default_song = r100.sng
2
+theme = default.thm
3
+title = default
4
+bpm = 90
5
+volume = 10

+ 95
- 0
lib/FSM.py View File

@@ -0,0 +1,95 @@
1
+
2
+import StatesProg
3
+   
4
+#====================================
5
+class Transition(object):
6
+    def __init__(self, toState):
7
+        self.toState = toState
8
+    def Execute(self):
9
+        pass
10
+                        
11
+#===================================
12
+                        
13
+class FSM(object):
14
+    def __init__ (self, character, owner):
15
+        self.char = character
16
+        self.states = {}
17
+        self.transitions = {}
18
+        self.curState = None
19
+        self.prevState = None
20
+        self.trans = None
21
+        self.stateLife = 0
22
+        self.owner = owner
23
+        self.name = None
24
+        self.last_state_notes = None
25
+        
26
+    def AddTransition(self, transName, transition):
27
+        self.transitions[transName] = transition
28
+    
29
+    def AddState(self, stateName, state):
30
+        self.states[stateName] = state
31
+        self.states[stateName].name = state        
32
+        
33
+    def SetState(self, stateName):
34
+        self.prevState = self.curState
35
+        self.curState = self.states[stateName]
36
+        
37
+    def ToTransition(self, toTrans):
38
+        self.trans = self.transitions[toTrans]
39
+        
40
+    def Execute(self, owner):
41
+        if (self.trans):
42
+            self.owner = owner
43
+            self.curState.Exit()
44
+            self.trans.Execute()
45
+            self.SetState(self.trans.toState)
46
+            self.curState.Enter()
47
+            self.trans = None        
48
+            
49
+        self.curState.Execute()    
50
+                
51
+#====================================
52
+
53
+Char = type("Char",(object,),{})
54
+
55
+#===================================
56
+          
57
+
58
+#=================================== 
59
+
60
+class ProgFSM(Char):
61
+    def __init__(self, owner):
62
+        self.FSM = FSM(self, owner)
63
+        self.owner = owner
64
+        
65
+        state_list = [
66
+        'Example',
67
+        'Main',
68
+        'Startup',
69
+        'Intro',
70
+        'LoadDefault',
71
+        'PadPlayer',
72
+        'SeqPlayer',
73
+        'SelectSound',
74
+        'SelectPattern',
75
+        'EditSoundSequence',
76
+        'EditSong',
77
+        'Util', 
78
+        'File',
79
+        'Ball',
80
+        'Clock'
81
+        ]
82
+        
83
+        for s in state_list:
84
+            self.FSM.AddState(s, getattr(StatesProg, s)(self.FSM))
85
+            t = 'to' + s
86
+            self.FSM.AddTransition(t, Transition(s))
87
+        
88
+        if self.FSM.curState == None:
89
+            self.FSM.SetState('Startup')
90
+    
91
+    def Execute(self):
92
+        self.FSM.Execute(self.owner)    
93
+
94
+#=================================== 
95
+        

+ 1301
- 0
lib/StatesProg.py View File

@@ -0,0 +1,1301 @@
1
+
2
+import random
3
+import time
4
+import pygame
5
+
6
+import pyaudio
7
+import wave
8
+import glob
9
+import os
10
+from itertools import cycle
11
+from datetime import datetime
12
+import ast
13
+#====================================     
14
+
15
+State = type("State", (object,), {})
16
+#====================================     
17
+class State(object):
18
+	def __init__(self, FSM):
19
+		self.FSM = FSM
20
+		self.timer = 0
21
+		self.startTime = 0
22
+	def Enter(self):
23
+		self.timer = 0
24
+		self.startTime = 0
25
+	def Execute(self):
26
+		print('Executing')
27
+	def Exit(self):
28
+		print('Exiting')
29
+
30
+#==================================== 
31
+
32
+def play_seq(o, message):
33
+	_id = 0
34
+	for s in o.soundSlots:
35
+		#print('mes ', message)
36
+		#print('thi ', s.notes[message[0]][message[1]])
37
+		#print(s.notes[message[0]])
38
+
39
+		#if s.notes[message[1]][0] == 1:
40
+		if s.notes[message[0]][message[1]][0] == 1:
41
+			#o.soundSlots[_id].set_volume(s.notes[message[0]][message[1]][1])
42
+			o.soundSlots[_id].play(s.notes[message[0]][message[1]][1])
43
+			#print('play')
44
+			#o.soundSlots[_id].play(s.notes[message[1]][1])
45
+			#o.soundSlots[_id].play(s.notes[message[0]])
46
+		_id += 1
47
+
48
+def draw_header(o):
49
+	o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
50
+	o.center_text(o.header_text, o.h1, o.width, 25, o.light_grey)
51
+	o.center_text("----------------------", o.h2, o.width, 55, o.light_grey)
52
+	#o.draw.text((0, 0), o.header_text, font=o.h1, fill=o.light_grey)
53
+	#o.disp.image(o.image, o.rotation)
54
+			
55
+def text_box1(o, a, b):
56
+	w1 = 0
57
+	w2 = (o.width / 2) - 50
58
+	h1 = 45
59
+	h2 = h1 + 10
60
+	o.center_block(a, o.h2, [w1,h1,w2,h2], o.light_grey)
61
+	o.center_block(b, o.h2, [w1,h1+10,w2,h2+25], o.light_grey)
62
+
63
+def text_box2(o, a, b):
64
+	w1 = 0
65
+	w2 = (o.width / 2) - 50
66
+	h1 = 90
67
+	h2 = h1 + 10
68
+	o.center_block(a, o.h2, [w1,h1,w2,h2], o.light_grey)
69
+	o.center_block(b, o.h2, [w1,h1+10,w2,h2+25], o.light_grey)
70
+
71
+def text_box3(o, a, b):
72
+	w1 = (o.width / 2) + 40
73
+	w2 = o.width
74
+	h1 = 45
75
+	h2 = h1 + 10
76
+	o.center_block(a, o.h2, [w1,h1,w2,h2], o.light_grey)
77
+	o.center_block(b, o.h2, [w1,h1+10,w2,h2+25], o.light_grey)
78
+
79
+def text_box4(o, a, b):
80
+	w1 = (o.width / 2) + 40
81
+	w2 = o.width
82
+	h1 = 90
83
+	h2 = h1 + 10
84
+	o.center_block(a, o.h2, [w1,h1,w2,h2], o.light_grey)
85
+	o.center_block(b, o.h2, [w1,h1+10,w2,h2+25], o.light_grey)
86
+
87
+def text_center(o, a, b):
88
+	w1 = 0
89
+	w2 = o.width
90
+	h1 = 45
91
+	h2 = h1 + 10
92
+	o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
93
+	o.center_block(a, o.h1, [w1,h1,w2,h2], o.light_grey)
94
+	o.center_block(b, o.h1, [w1,h1+20,w2,h2+45], o.light_grey)
95
+
96
+
97
+def draw_menu1(o):
98
+	#o = self.FSM.owner 
99
+	bpm_inc = 4
100
+	x_size = o.width / 4
101
+	y_size = o.height / 4
102
+	og_x = 0
103
+	o_x = og_x
104
+	o_y = o.height
105
+	text_padding = 6
106
+	_id = 0
107
+	while _id < 16:
108
+	   
109
+		o.draw.rectangle((o_x, o_y, o_x + x_size, o_y - y_size), outline=0, fill=o.blue)
110
+
111
+		if _id == 0:
112
+			o.center_block("Main", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
113
+		if _id == 1:
114
+			o.center_block("Note", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
115
+		if _id == 2:
116
+			o.center_block("Patt", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
117
+		if _id == 3:
118
+			o.center_block("Song", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
119
+		if _id == 4:
120
+			o.center_block("File", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
121
+		if _id == 5:
122
+			o.center_block("Util", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
123
+
124
+		if _id == 6:
125
+			o.center_block("Bank-", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
126
+			
127
+		if _id == 7:
128
+			o.center_block("Bank+", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
129
+			
130
+		if _id == 8:
131
+			o.center_block("Vol-", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
132
+			#if o.volume > 0:
133
+				#o.volume -= 1
134
+		if _id == 9:
135
+			o.center_block("Vol+", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
136
+			#if o.volume < 16:
137
+				#o.volume += 1
138
+		if _id == 11:
139
+			o.center_block("Save", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
140
+		if _id == 12:
141
+			o.center_block("Play", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
142
+		if _id == 13:
143
+			o.center_block("Stop", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
144
+		if _id == 14:
145
+			o.center_block("BPM-", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
146
+		if _id == 15:
147
+			o.center_block("BPM+", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
148
+			
149
+
150
+		o_x = o_x + x_size
151
+		_id += 1
152
+		if _id % 4 == 0:
153
+			o_y -= y_size
154
+			o_x = og_x
155
+
156
+
157
+def draw_menu2(o):
158
+	#o = self.FSM.owner 
159
+	bpm_inc = 4
160
+	x_size = o.width / 4
161
+	y_size = o.height / 4
162
+	og_x = 0
163
+	o_x = og_x
164
+	o_y = o.height
165
+	text_padding = 6
166
+	_id = 0
167
+	while _id < 16:
168
+	   
169
+		o.draw.rectangle((o_x, o_y, o_x + x_size, o_y - y_size), outline=0, fill=o.dark_grey)
170
+
171
+		# if _id == 0:
172
+		#     o.center_block("Main", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
173
+		# if _id == 1:
174
+		#     o.center_block("Note", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
175
+		# if _id == 2:
176
+		#     o.center_block("Patt", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
177
+		# if _id == 3:
178
+		#     o.center_block("Song", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
179
+		# if _id == 4:
180
+		#     o.center_block("File", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
181
+		# if _id == 5:
182
+		#     o.center_block("Util", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
183
+
184
+		# if _id == 6:
185
+		#     o.center_block("Bank-", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
186
+			
187
+		# if _id == 7:
188
+		#     o.center_block("Bank+", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
189
+			
190
+		# if _id == 8:
191
+		#     o.center_block("Vol-", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
192
+		#     #if o.volume > 0:
193
+		#         #o.volume -= 1
194
+		# if _id == 9:
195
+		#     o.center_block("Vol+", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
196
+		#     #if o.volume < 16:
197
+		#         #o.volume += 1
198
+		# if _id == 11:
199
+		#     o.center_block("Save", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
200
+		# if _id == 12:
201
+		#     o.center_block("Play", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
202
+		# if _id == 13:
203
+		#     o.center_block("Stop", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
204
+		# if _id == 14:
205
+		#     o.center_block("BPM-", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
206
+		# if _id == 15:
207
+		#     o.center_block("BPM+", o.h2, [o_x, o_y, o_x + x_size, o_y - y_size], o.light_grey)
208
+			
209
+
210
+		o_x = o_x + x_size
211
+		_id += 1
212
+		if _id % 4 == 0:
213
+			o_y -= y_size
214
+			o_x = og_x
215
+
216
+
217
+
218
+
219
+
220
+def menu1_actions(self, o):
221
+	if o.keyState[16] == 2:
222
+		if o.keyState[0] == 1:
223
+			self.FSM.ToTransition('toMain')
224
+		elif o.keyState[1] == 1:
225
+			self.FSM.ToTransition('toEditSoundSequence')
226
+		elif o.keyState[2] == 1:
227
+			self.FSM.ToTransition('toSelectPattern')
228
+		elif o.keyState[3] == 1:
229
+			self.FSM.ToTransition('toEditSong')
230
+		elif o.keyState[4] == 1:
231
+			self.FSM.ToTransition('toFile')
232
+		elif o.keyState[5] == 1:
233
+			self.FSM.ToTransition('toClock')
234
+		if o.keyState[12] == 1:
235
+			o.start_playback()
236
+		if o.keyState[13] == 1:
237
+			o.stop_playback()
238
+		if o.keyState[6] == 1:
239
+			if o.note_bank > 0:
240
+				o.note_bank -= 1
241
+		if o.keyState[7] == 1:
242
+			if o.note_bank < 4:
243
+				o.note_bank += 1
244
+		if o.keyState[8] == 1:
245
+			if o.volume > 0:
246
+				o.volume -= 1
247
+				text_center(o, "Master Volume", str(o.volume))
248
+				o.disp.image(o.image, o.rotation)
249
+		if o.keyState[9] == 1:
250
+			if o.volume < 16:
251
+				o.volume += 1
252
+				text_center(o, "Master Volume", str(o.volume))
253
+				o.disp.image(o.image, o.rotation)
254
+		if o.keyState[11] == 1:
255
+			o.save_song()
256
+		if o.keyState[14] == 1:
257
+			if o.sconf.as_int('bpm') > 60:
258
+				o.sconf['bpm'] = o.sconf.as_int('bpm') - o.bpm_inc
259
+				o.update_bpm()
260
+				text_center(o, "Master BPM", str(o.sconf['bpm']))
261
+				o.disp.image(o.image, o.rotation)
262
+		if o.keyState[15] == 1:
263
+			if o.sconf.as_int('bpm') < 240:
264
+				o.sconf['bpm'] = o.sconf.as_int('bpm') + o.bpm_inc
265
+				o.update_bpm()
266
+				text_center(o, "Master BPM", str(o.sconf['bpm']))
267
+				o.disp.image(o.image, o.rotation)
268
+				
269
+
270
+class Example(State):
271
+	def __init__(self,FSM):
272
+		super(Example, self).__init__(FSM)    
273
+		
274
+	def Enter(self):
275
+		self.FSM.stateLife = 1
276
+		super(Example, self).Enter()        
277
+		
278
+	def Execute(self):
279
+		self.FSM.stateLife += 1
280
+		#self.FSM.ToTransition('toLand')
281
+		
282
+	def Exit(self):
283
+		pass
284
+
285
+#==================================== 
286
+
287
+class Startup(State):
288
+	def __init__(self,FSM):
289
+		super(Startup, self).__init__(FSM)    
290
+		
291
+	def Enter(self):
292
+		self.FSM.stateLife = 1
293
+		self.birth = time.perf_counter()
294
+		super(Startup, self).Enter()        
295
+		
296
+	def Execute(self):
297
+		self.FSM.stateLife += 1
298
+		print('startup state')
299
+
300
+		if hasattr(self, 'birth'):
301
+			pass
302
+		else:
303
+			self.birth = time.perf_counter()
304
+
305
+		self.FSM.ToTransition('toIntro')
306
+		
307
+	def Exit(self):
308
+		pass
309
+
310
+#==================================== 
311
+			
312
+class Intro(State):
313
+	def __init__(self,FSM):
314
+		super(Intro, self).__init__(FSM)    
315
+		
316
+	def Enter(self):
317
+		self.FSM.stateLife = 1
318
+		o = self.FSM.owner
319
+		self.FSM.owner.draw.rectangle((0, 0, self.FSM.owner.width, self.FSM.owner.height), outline=0, fill=o.blue)
320
+		super(Intro, self).Enter()        
321
+		
322
+	def Execute(self):
323
+		self.FSM.stateLife += 1
324
+		o = self.FSM.owner
325
+		self.FSM.ToTransition('toLoadDefault')
326
+		self.FSM.owner.draw.rectangle((0, 0, self.FSM.owner.width, self.FSM.owner.height), outline=0, fill=o.blue)
327
+		#self.FSM.owner.draw.text((0, 0), str(self.FSM.owner.playhead), font=self.FSM.owner.font, fill="#FFFFFF")
328
+		self.FSM.owner.draw.text((0, 0), str(self.FSM.owner.playhead), font=self.FSM.owner.font, fill="#FF0000")
329
+		
330
+	def Exit(self):
331
+		pass
332
+
333
+#==================================== 
334
+			
335
+class LoadDefault(State):
336
+	def __init__(self,FSM):
337
+		super(LoadDefault, self).__init__(FSM)    
338
+		
339
+	def Enter(self):
340
+		self.FSM.stateLife = 1
341
+
342
+
343
+		o = self.FSM.owner
344
+		o.load_song()
345
+
346
+
347
+
348
+		self.bank = 0
349
+		self.sounds = []
350
+		self.globbed = glob.glob('/home/pi/skull/*.wav') + glob.glob('/home/pi/skull/*.mp3')
351
+		self.globbed.sort()
352
+		obj_id = 0
353
+		# for f in self.globbed:
354
+		#     #self.sounds.append(pygame.mixer.Sound(f))
355
+		#     p1 = self.FSM.owner.SoundSlot(f, obj_id)
356
+		#     self.FSM.owner.soundSlots.append(p1)
357
+		#     obj_id += 1
358
+
359
+		# self.sound_names = [os.path.basename(x) for x in self.globbed]
360
+
361
+		for s in o.sconf['sounds']:
362
+			#print('aaaaaayyy, ', s)
363
+			p1 = self.FSM.owner.SoundSlot(s, obj_id, o)
364
+			self.FSM.owner.soundSlots.append(p1)
365
+			obj_id += 1
366
+
367
+		#print(self.globbed)
368
+		#print(self.FSM.owner.soundSlots)
369
+		#pygame.mixer.Sound.play(self.FSM.owner.sounds[(self.FSM.owner.bank * 2)])  
370
+		#self.FSM.owner.soundSlots[2].play()
371
+
372
+		#o.sconf.to_list(o.sconf['notes'])
373
+		#lst = o.sconf.as_list('notes')
374
+
375
+		_iter = 0
376
+
377
+		lst = ast.literal_eval(o.sconf['notes'][0])
378
+		print('length ', len(lst))
379
+
380
+		for n in o.soundSlots:
381
+			#print(lst[_iter])
382
+			#print(n.notes)
383
+			#print('---', len(n.notes[0]), _iter)
384
+			m = []
385
+			lst = ast.literal_eval(o.sconf.as_list('notes')[_iter])
386
+			#n.notes = lst[_iter]
387
+			n.notes = lst
388
+			_iter += 1
389
+
390
+		#print(self.FSM.owner.soundSlots[0].notes[0])
391
+		# self.FSM.owner.soundSlots[0].notes[0] = [[1,16,0],[0,0,0],[0,0,0],[0,0,0], 
392
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
393
+		#                                         [0,0,0],[0,0,0],[1,16,0],[0,0,0], 
394
+		#                                         [0,0,0],[0,0,0],[1,16,0],[0,0,0]]
395
+		# self.FSM.owner.soundSlots[3].notes[0] = [[1,16,0],[0,0,0],[1,16,0],[1,16,0], 
396
+		#                                         [1,16,0],[0,0,0],[1,16,0],[1,16,0], 
397
+		#                                         [1,16,0],[1,16,0],[1,16,0],[0,0,0], 
398
+		#                                         [1,16,0],[0,0,0],[0,0,0],[0,0,0]]
399
+		# self.FSM.owner.soundSlots[2].notes[0] = [[0,0,0],[0,0,0],[0,0,0],[0,0,0], 
400
+		#                                         [1,16,0],[0,0,0],[0,0,0],[0,0,0], 
401
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
402
+		#                                         [1,16,0],[0,0,0],[0,0,0],[0,0,0]]
403
+
404
+		# self.FSM.owner.soundSlots[0].notes[1] = [[1,16,0],[0,0,0],[0,0,0],[0,0,0], 
405
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
406
+		#                                         [0,0,0],[0,0,0],[1,16,0],[0,0,0], 
407
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0]]
408
+		# self.FSM.owner.soundSlots[3].notes[1] = [[1,16,0],[0,0,0],[1,16,0],[0,0,0], 
409
+		#                                         [1,16,0],[0,0,0],[1,16,0],[1,16,0], 
410
+		#                                         [1,16,0],[0,0,0],[1,16,0],[0,0,0], 
411
+		#                                         [1,16,0],[0,0,0],[0,0,0],[0,0,0]]
412
+		# self.FSM.owner.soundSlots[2].notes[1] = [[0,0,0],[0,0,0],[0,0,0],[0,0,0], 
413
+		#                                         [1,16,0],[0,0,0],[0,0,0],[0,0,0], 
414
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
415
+		#                                         [1,16,0],[0,0,0],[0,0,0],[0,0,0]]
416
+
417
+		# self.FSM.owner.soundSlots[0].notes[2] = [[0,0,0],[0,0,0],[0,0,0],[0,0,0], 
418
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
419
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
420
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0]]
421
+		# self.FSM.owner.soundSlots[3].notes[2] = [[1,16,0],[0,0,0],[1,16,0],[0,0,0], 
422
+		#                                         [1,16,0],[0,0,0],[1,16,0],[1,16,0], 
423
+		#                                         [1,16,0],[0,0,0],[1,16,0],[0,0,0], 
424
+		#                                         [1,16,0],[0,0,0],[1,16,0],[0,0,0]]
425
+		# self.FSM.owner.soundSlots[2].notes[2] = [[0,0,0],[0,0,0],[0,0,0],[0,0,0], 
426
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
427
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
428
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0]]
429
+
430
+		# self.FSM.owner.soundSlots[0].notes[3] = [[0,0,0],[0,0,0],[0,0,0],[0,0,0], 
431
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
432
+		#                                         [1,16,0],[0,0,0],[0,0,0],[0,0,0], 
433
+		#                                         [1,16,0],[0,0,0],[1,16,0],[0,0,0]]
434
+		# self.FSM.owner.soundSlots[3].notes[3] = [[1,16,0],[0,0,0],[1,16,0],[0,0,0], 
435
+		#                                         [1,16,0],[0,0,0],[1,16,0],[1,16,0], 
436
+		#                                         [1,16,0],[0,0,0],[1,16,0],[0,0,0], 
437
+		#                                         [1,16,0],[0,0,0],[1,16,0],[0,0,0]]
438
+		# self.FSM.owner.soundSlots[2].notes[3] = [[0,0,0],[0,0,0],[0,0,0],[0,0,0], 
439
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
440
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0], 
441
+		#                                         [0,0,0],[0,0,0],[0,0,0],[0,0,0]]
442
+
443
+		#for n in self.FSM.owner.soundSlots[0].notes:
444
+				#print('sound ', self.id, ' ', n)
445
+
446
+		super(LoadDefault, self).Enter()        
447
+		
448
+	def Execute(self):
449
+		self.FSM.stateLife += 1
450
+		#self.FSM.owner.save_song()
451
+		self.FSM.ToTransition('toMain')
452
+		#self.FSM.ToTransition('toMain')        
453
+		
454
+	def Exit(self):
455
+		pass
456
+
457
+#==================================== 
458
+
459
+#==================================== 
460
+			
461
+class Main(State):
462
+	def __init__(self,FSM):
463
+		super(Main, self).__init__(FSM)    
464
+		
465
+	def Enter(self):
466
+		print('hello main')
467
+		self.FSM.owner.pub.register("beat", self)
468
+		self.FSM.stateLife = 1
469
+		o = self.FSM.owner 
470
+		o.header_text = "Main"
471
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
472
+			pass
473
+		else:
474
+			draw_header(o)
475
+			self.draw_square()
476
+			text_box1(o, "Vol", str(o.sconf['volume']))
477
+			text_box2(o, "BPM", str(o.sconf['bpm']))
478
+			text_box4(o, "Bank", str(o.note_bank))
479
+			#text_box3(o, "BPM", "90.0")
480
+			o.disp.image(o.image, o.rotation)
481
+
482
+		#o.start_playback() 
483
+
484
+		#should be pattner len 16
485
+		print('this sound ', o.soundSlots[0].notes[0])
486
+		o.soundSlots[2].volume = 2
487
+		super(Main, self).Enter()        
488
+		
489
+	def Execute(self):
490
+		o = self.FSM.owner 
491
+		# if o.keyState[16] > 0:
492
+		#     print('okey ', o.keyState[16])
493
+
494
+		menu1_actions(self, o)
495
+		
496
+		if o.keyState[16] == 1:
497
+			draw_menu1(o)
498
+			o.disp.image(o.image, o.rotation)
499
+		elif o.keyState[16] == 4:
500
+			draw_header(o)
501
+			self.draw_square()
502
+			text_box1(o, "Vol", str(o.volume))
503
+			text_box2(o, "BPM", str(o.sconf['bpm']))
504
+			text_box4(o, "Bank", str(o.note_bank))
505
+			#text_box3(o, "BPM", "90.0")
506
+			o.disp.image(o.image, o.rotation)
507
+
508
+
509
+		if o.keyState[17] == 1:
510
+			draw_menu2(o)
511
+			o.disp.image(o.image, o.rotation)
512
+		elif o.keyState[17] == 4:
513
+			draw_header(o)
514
+			self.draw_square()
515
+			text_box1(o, "Vol", str(o.volume))
516
+			text_box2(o, "BPM", str(o.sconf['bpm']))
517
+			text_box4(o, "Bank", str(o.note_bank))
518
+			#text_box3(o, "BPM", "90.0")
519
+			o.disp.image(o.image, o.rotation)
520
+
521
+
522
+
523
+		# if o.keyState[17] == 1:
524
+		#     o.draw.rectangle((20, 100, 40, 120), outline=0, fill=o.color_b)
525
+		#     o.disp.image(o.image, o.rotation)
526
+		# elif o.keyState[17] == 4:
527
+		#     o.draw.rectangle((20, 100, 40, 120), outline=0, fill=o.dark_grey)
528
+		#     o.disp.image(o.image, o.rotation)
529
+
530
+		_id = 0
531
+		for k in o.keyState:
532
+			if _id > 15:
533
+				pass
534
+			else:
535
+				if o.keyState[16] > 0 or o.keyState[17] > 0:
536
+					pass
537
+				else:
538
+					if k == 1:
539
+						#_id2
540
+						#o.eSound = _id
541
+
542
+						#o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
543
+						#o.draw.text((0, 0), "SelSou", font=o.h1, fill=o.color_a)
544
+						#o.draw.text((20, 60), str(o.eSound), font=o.h2, fill=o.color_b)
545
+						note = _id + o.note_bank * 16
546
+						if len(o.soundSlots) > note:
547
+							
548
+							self.FSM.owner.soundSlots[note].play(o.note_vol)
549
+							o.eSound = note
550
+							print('now editing sound ', o.eSound) 
551
+							#draw_header(o)            
552
+							#self.draw_square()  
553
+							text_box1(o, "Vol", str(o.volume))
554
+							text_box2(o, "BPM", str(o.sconf['bpm']))
555
+							text_box4(o, "Bank", str(o.note_bank))          
556
+							self.draw_square()
557
+							o.disp.image(o.image, o.rotation)
558
+			_id += 1
559
+
560
+
561
+
562
+	def ReceiveMessage(self, message):
563
+		_id = 0
564
+		o = self.FSM.owner 
565
+		play_seq(o, message)
566
+		# for s in o.soundSlots:
567
+		#     print('--', s.notes[message[0]][message[1]])
568
+		#     if s.notes[message[0]][message[1]][0] == 1:
569
+		#         o.soundSlots[_id].play()
570
+		#     _id += 1
571
+
572
+		_id = 0
573
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
574
+			pass
575
+		else:
576
+			draw_header(o)            
577
+			self.draw_square()            
578
+	
579
+
580
+	def draw_square(self):
581
+		o = self.FSM.owner 
582
+		size = 22
583
+		og_x = (o.width / 2) - (size * 2)
584
+		o_x = og_x
585
+		o_y = 127
586
+		_id = o.note_bank * 16 
587
+		#o.draw.rectangle((o_x + 4, o_y + 4, o_x + (size *4) + 4 , (o_y - (size * 4) + 4)), outline="#000000", fill="#000000", width=1)
588
+		#for n in o.soundSlots[o.eSound].notes[o.ePattern]:
589
+		while _id < ((o.note_bank * 16) + 16):
590
+			#print('n is ' ,n)
591
+			if _id == o.eSound:
592
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.light_grey, fill=o.light_grey, width=1)
593
+			else:
594
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.light_grey, fill=o.blue, width=1)
595
+			o_x = o_x + size
596
+			_id += 1
597
+			if _id % 4 == 0:
598
+				o_y -= size
599
+				o_x = og_x
600
+
601
+	def Exit(self):
602
+		self.FSM.owner.pub.unregister("beat", self)
603
+
604
+#==================================== 
605
+#==================================== 
606
+			
607
+class PadPlayer(State):
608
+	def __init__(self,FSM):
609
+		super(PadPlayer, self).__init__(FSM)    
610
+		
611
+	def Enter(self):
612
+		self.FSM.stateLife = 1
613
+		print('entering pad player')
614
+		super(PadPlayer, self).Enter()        
615
+		
616
+	def Execute(self):
617
+		self.FSM.stateLife += 1
618
+
619
+
620
+		_id = 0
621
+		for k in self.FSM.owner.keyState:
622
+			if _id < 16 and k == 1:
623
+				self.FSM.owner.soundSlots[_id].play()
624
+			_id += 1
625
+		if self.FSM.owner.keys:
626
+			
627
+			print("Pressed: ", self.FSM.owner.keys)
628
+		#self.FSM.ToTransition('toLand')
629
+		
630
+	def Exit(self):
631
+		pass
632
+
633
+#==================================== 
634
+			
635
+class SeqPlayer(State):
636
+	def __init__(self,FSM):
637
+		super(SeqPlayer, self).__init__(FSM)    
638
+		
639
+	def Enter(self):
640
+		self.FSM.stateLife = 1
641
+		self.beat = 0
642
+		self.FSM.owner.pub.register("beat", self)
643
+		print('entering seq player')
644
+		o = self.FSM.owner
645
+		o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
646
+		o.draw.text((20, 0), 'SeqPlayer', font=o.h1, fill="#FFFFFF")
647
+		o.disp.image(o.image, o.rotation)
648
+		super(SeqPlayer, self).Enter()        
649
+		
650
+	def Execute(self):
651
+		self.FSM.stateLife += 1
652
+		o = self.FSM.owner
653
+		# if o.playhead != self.beat:
654
+		#     #print('beat')
655
+		#     o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=(0, 0, 0))
656
+		#     o.draw.text((0, 0), str(o.playhead), font=o.font, fill="#FFFFFF")
657
+		#     #if (o.playhead % 8) == 0:
658
+		#     o.disp.image(o.image, o.rotation)
659
+
660
+
661
+		#     _id = 0
662
+		#     for s in o.soundSlots:
663
+		#         #if s.pattern[o.playhead] == 1:
664
+		#         if s.notes[o.curPattern][o.playhead] == 1:
665
+		#             o.soundSlots[_id].play()
666
+		#         _id += 1
667
+		if o.keyState[12] == 1:
668
+			o.start_playback()
669
+		if o.keyState[13] == 1:
670
+			o.stop_playback()        
671
+		#self.beat = o.playhead
672
+		#print('key0 ', o.keyState[16])
673
+		if o.keyState[16] == 2:
674
+			#print('key0 ', o.keyState[16])
675
+			if o.keyState[0] == 1:
676
+				print('to main')
677
+				self.FSM.ToTransition('toMain')
678
+
679
+	def ReceiveMessage(self, message):
680
+		#print(self.name, 'got message', message)
681
+		o = self.FSM.owner
682
+		o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
683
+		o.draw.text((20, 0), str(message[0]) + ' - ' + str(message[1]), font=o.h1, fill="#FFFFFF")
684
+		o.disp.image(o.image, o.rotation)
685
+		play_seq(o, message)
686
+		# _id = 0
687
+		# for s in o.soundSlots:
688
+		#     if s.notes[message[0]][message[1]][0] == 1:
689
+		#         o.soundSlots[_id].play()
690
+		#     _id += 1
691
+
692
+	def Exit(self):
693
+		self.FSM.owner.pub.unregister("beat", self)
694
+
695
+#==================================== 
696
+#==================================== 
697
+			
698
+class SelectSound(State):
699
+	def __init__(self,FSM):
700
+		super(SelectSound, self).__init__(FSM)    
701
+		
702
+	def Enter(self):
703
+		self.FSM.stateLife = 1
704
+		o = self.FSM.owner 
705
+		o.header_text = "SelSou"
706
+		o.pub.register("beat", self)
707
+		#o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=(0, 0, 0))
708
+		#o.draw.text((0, 0), "SelSou", font=o.h1, fill="#FFFFFF")
709
+		draw_header(o)
710
+		self.draw_square()
711
+		o.disp.image(o.image, o.rotation)
712
+		super(SelectSound, self).Enter()        
713
+		
714
+	def Execute(self):
715
+		o = self.FSM.owner 
716
+		if o.keyState[16] == 1:
717
+			draw_menu1(o)
718
+			o.disp.image(o.image, o.rotation)
719
+		elif o.keyState[16] == 4:
720
+			draw_header(o)
721
+			self.draw_square()
722
+			o.disp.image(o.image, o.rotation)
723
+
724
+		if o.keyState[16] == 2:
725
+			if o.keyState[0] == 1:
726
+				self.FSM.ToTransition('toMain')
727
+		else:
728
+			_id = 0
729
+			for k in o.keyState:
730
+				if _id > 15:
731
+					pass
732
+				else:
733
+					if k == 1:
734
+						o.eSound = _id
735
+
736
+						o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
737
+						o.draw.text((0, 0), "SelSou", font=o.h1, fill=o.color_a)
738
+						o.draw.text((20, 60), str(o.eSound), font=o.h2, fill=o.color_b)
739
+
740
+						self.draw_square()
741
+						o.disp.image(o.image, o.rotation)
742
+						self.FSM.owner.soundSlots[_id].play()
743
+						
744
+						print('now editing sound ', _id)    
745
+
746
+							   
747
+				  
748
+
749
+				_id += 1        
750
+		#self.FSM.ToTransition('toLand')
751
+
752
+	def ReceiveMessage(self, message):
753
+		_id = 0
754
+		o = self.FSM.owner 
755
+		o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=(0, 0, 0))
756
+		o.draw.text((0, 0), "SelSou", font=o.h1, fill="#FFFFFF")
757
+		#o.disp.image(o.image, o.rotation)
758
+
759
+		#o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=(0, 0, 0))
760
+		o.draw.text((0, 20), str(o.eSound), font=o.h2, fill="#FFFFFF")
761
+		o.disp.image(o.image, o.rotation)
762
+		play_seq(o, message)
763
+		# for s in o.soundSlots:
764
+		#     if s.notes[message[0]][message[1]][0] == 1:
765
+		#         o.soundSlots[_id].play()
766
+		#     _id += 1
767
+
768
+	def draw_square(self):
769
+		o = self.FSM.owner 
770
+		size = 32
771
+		og_x = 110
772
+		o_x = og_x
773
+		o_y = 0
774
+		_id = 0
775
+	
776
+		for n in o.soundSlots[o.eSound].notes[o.ePattern]:
777
+			#print('n is ' ,n)
778
+			if _id == o.eSound:
779
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y + size), outline=0, fill=o.color_b)
780
+			else:
781
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y + size), outline=0, fill=o.dark_grey)
782
+			o_x = o_x + size
783
+			_id += 1
784
+			if _id % 4 == 0:
785
+				o_y += size
786
+				o_x = og_x
787
+
788
+		
789
+	def Exit(self):
790
+		self.FSM.owner.pub.unregister("beat", self)
791
+
792
+#==================================== 
793
+#==================================== 
794
+			
795
+class SelectPattern(State):
796
+	def __init__(self,FSM):
797
+		super(SelectPattern, self).__init__(FSM)    
798
+		
799
+	def Enter(self):
800
+		self.FSM.stateLife = 1
801
+		o = self.FSM.owner 
802
+		o.header_text = "Select Pattern"
803
+		o.pub.register("beat", self)
804
+
805
+
806
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
807
+			pass
808
+		else:
809
+			draw_header(o)
810
+			self.draw_square()
811
+			o.disp.image(o.image, o.rotation)
812
+		
813
+		super(SelectPattern, self).Enter()        
814
+		
815
+	def Execute(self):
816
+		o = self.FSM.owner 
817
+		menu1_actions(self, o)
818
+		if o.keyState[16] == 1:
819
+			draw_menu1(o)
820
+			o.disp.image(o.image, o.rotation)
821
+		elif o.keyState[16] == 4:
822
+			draw_header(o)
823
+			self.draw_square()
824
+			o.disp.image(o.image, o.rotation)
825
+
826
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
827
+			pass
828
+		#     if o.keyState[0] == 1:
829
+		#         self.FSM.ToTransition('toMain')
830
+		else:
831
+			_id = 0
832
+			for k in o.keyState:
833
+				if _id > 15:
834
+					pass
835
+				else:
836
+					if k == 1:
837
+						o.ePattern = _id
838
+
839
+						o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
840
+						o.draw.text((0, 0), "SelPat", font=o.h1, fill=o.color_a)
841
+						o.draw.text((20, 60), str(o.ePattern), font=o.h2, fill=o.color_b)
842
+						self.draw_square()
843
+						o.disp.image(o.image, o.rotation)
844
+						
845
+						print('now editing sound ', _id)                
846
+
847
+				_id += 1        
848
+
849
+	def ReceiveMessage(self, message):
850
+		_id = 0
851
+		o = self.FSM.owner
852
+		play_seq(o, message) 
853
+		# for s in o.soundSlots:
854
+		#     if s.notes[message[0]][message[1]][0] == 1:
855
+		#         o.soundSlots[_id].play()
856
+		#     _id += 1
857
+
858
+	def draw_square(self):
859
+		o = self.FSM.owner 
860
+		size = 22
861
+		og_x = (o.width / 2) - (size * 2)
862
+		o_x = og_x
863
+		o_y = 127
864
+		_id = 0
865
+		for n in o.soundSlots[o.eSound].notes[o.ePattern]:
866
+			if _id == o.ePattern:
867
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.olive, fill=o.olive, width=1)
868
+			else:
869
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.light_grey, fill=o.blue, width=1)
870
+			o_x = o_x + size
871
+			_id += 1
872
+			if _id % 4 == 0:
873
+				o_y -= size
874
+				o_x = og_x
875
+
876
+#////////////////////////////////////////////
877
+	
878
+		# for n in o.soundSlots[o.eSound].notes[o.ePattern]:
879
+		#     #print('n is ' ,n)
880
+		#     if _id == o.ePattern:
881
+		#         o.draw.rectangle((o_x, o_y, o_x + size, o_y + size), outline=0, fill=o.color_b)
882
+		#     else:
883
+		#         o.draw.rectangle((o_x, o_y, o_x + size, o_y + size), outline=0, fill=o.dark_grey)
884
+		#     o_x = o_x + size
885
+		#     _id += 1
886
+		#     if _id % 4 == 0:
887
+		#         o_y += size
888
+		#         o_x = og_x            
889
+
890
+	def Exit(self):
891
+		self.FSM.owner.pub.unregister("beat", self)
892
+
893
+#==================================== 
894
+			
895
+class EditSoundSequence(State):
896
+	def __init__(self,FSM):
897
+		super(EditSoundSequence, self).__init__(FSM)    
898
+		
899
+	def Enter(self):
900
+		self.FSM.stateLife = 1
901
+		o = self.FSM.owner 
902
+		o.header_text = "Note Edit"
903
+		o.pub.register("beat", self)
904
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
905
+			pass
906
+		else:
907
+			draw_header(o)
908
+			self.draw_square()
909
+			o.disp.image(o.image, o.rotation)
910
+		super(EditSoundSequence, self).Enter()        
911
+		
912
+	def Execute(self):
913
+		o = self.FSM.owner 
914
+		menu1_actions(self, o)
915
+		if o.keyState[16] == 1:
916
+			draw_menu1(o)
917
+			o.disp.image(o.image, o.rotation)
918
+		elif o.keyState[16] == 4:
919
+			draw_header(o)
920
+			self.draw_square()
921
+			o.disp.image(o.image, o.rotation)
922
+
923
+		if o.keyState[16] == 2:
924
+			pass
925
+
926
+		else:
927
+			_id = 0
928
+			for k in self.FSM.owner.keyState:
929
+				if _id < 16 and k == 1:
930
+					if o.soundSlots[o.eSound].notes[o.ePattern][_id][0] == 1:
931
+						print('turn note off')
932
+						o.soundSlots[o.eSound].notes[o.ePattern][_id][0] = 0
933
+					else:
934
+						print('turn note on')
935
+						o.soundSlots[o.eSound].notes[o.ePattern][_id][0] = 1
936
+					#self.FSM.owner.soundSlots[_id].play()
937
+
938
+				_id += 1
939
+
940
+		
941
+	def draw_square(self):
942
+		o = self.FSM.owner 
943
+		size = 22
944
+		og_x = (o.width / 2) - (size * 2)
945
+		o_x = og_x
946
+		o_y = 127
947
+		_id = 0
948
+		for n in o.soundSlots[o.eSound].notes[o.ePattern]:
949
+			if _id == o.playhead:
950
+				if o.curPattern == o.ePattern:
951
+					o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.olive, fill=o.olive, width=1)
952
+				else:
953
+					o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.olive, fill=o.pink, width=1)
954
+			elif n[0] == 1:
955
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.light_grey, fill=o.light_grey, width=1)
956
+			else:
957
+				o.draw.rectangle((o_x, o_y, o_x + size, o_y - size), outline=o.light_grey, fill=o.blue, width=1)
958
+			o_x = o_x + size
959
+			_id += 1
960
+			if _id % 4 == 0:
961
+				o_y -= size
962
+				o_x = og_x
963
+
964
+	def ReceiveMessage(self, message):
965
+		_id = 0
966
+		o = self.FSM.owner 
967
+		play_seq(o, message)
968
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
969
+			pass
970
+		else:
971
+			draw_header(o)            
972
+			self.draw_square()  
973
+		
974
+		o.disp.image(o.image, o.rotation)
975
+		
976
+		# for s in o.soundSlots:
977
+		#     if s.notes[message[0]][message[1]][0] == 1:
978
+		#         o.soundSlots[_id].play()
979
+		#     _id += 1            
980
+	def Exit(self):
981
+		self.FSM.owner.pub.unregister("beat", self)
982
+
983
+#==================================== 
984
+			
985
+class EditSong(State):
986
+	def __init__(self,FSM):
987
+		super(EditSong, self).__init__(FSM)    
988
+		
989
+	def Enter(self):
990
+		o = self.FSM.owner 
991
+		o.header_text = "Edit Song"
992
+		o.pub.register("beat", self)
993
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
994
+			pass
995
+		else:
996
+			draw_header(o)
997
+			o.draw.text((20, 30), "Current Song", font=o.h2, fill=o.light_grey)
998
+			o.draw.text((20, 50), str(o.song), font=o.h2, fill=o.light_grey)
999
+			o.disp.image(o.image, o.rotation)
1000
+		self.new_song = []
1001
+		super(EditSong, self).Enter()        
1002
+		
1003
+	def Execute(self):
1004
+		o = self.FSM.owner 
1005
+
1006
+		menu1_actions(self, o)
1007
+		if o.keyState[16] == 1:
1008
+			draw_menu1(o)
1009
+			o.disp.image(o.image, o.rotation)
1010
+		elif o.keyState[16] == 4:
1011
+			draw_header(o)
1012
+			if self.new_song == []:
1013
+				o.draw.text((20, 30), "Current Song", font=o.h2, fill=o.light_grey)
1014
+				o.draw.text((20, 50), str(o.song), font=o.h2, fill=o.light_grey)
1015
+			else:
1016
+				o.draw.text((20, 30), "New Song", font=o.h2, fill=o.pink)
1017
+				o.draw.text((20, 50), str(self.new_song), font=o.h2, fill=o.pink)
1018
+			#self.draw_square()
1019
+			o.disp.image(o.image, o.rotation)
1020
+
1021
+		if o.keyState[16] > 0:
1022
+			pass
1023
+		else:
1024
+			_id = 0
1025
+			for k in self.FSM.owner.keyState:
1026
+				if k == 1 and _id < 16:
1027
+					self.new_song.append(_id)
1028
+					#o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
1029
+					draw_header(o)
1030
+					#o.draw.text((0, 0), "SonEdt", font=o.h1, fill=o.light_grey)
1031
+					o.draw.text((20, 30), "New Song", font=o.h2, fill=o.pink)
1032
+					o.draw.text((20, 50), str(self.new_song), font=o.h2, fill=o.pink)
1033
+					o.disp.image(o.image, o.rotation)
1034
+				_id += 1        
1035
+
1036
+	def ReceiveMessage(self, message):
1037
+		_id = 0
1038
+		o = self.FSM.owner 
1039
+		play_seq(o, message)
1040
+		# for s in o.soundSlots:
1041
+		#     if s.notes[message[0]][message[1]][0] == 1:
1042
+		#         o.soundSlots[_id].play()
1043
+		#     _id += 1
1044
+
1045
+	def Exit(self):
1046
+		o = self.FSM.owner 
1047
+		self.FSM.owner.pub.unregister("beat", self)
1048
+		if self.new_song != []:
1049
+			o.song = self.new_song
1050
+			o.songCycle = cycle(self.new_song)
1051
+			self.curPattern = next(o.songCycle)
1052
+			o.songStart = o.curPattern
1053
+
1054
+#==================================== 
1055
+
1056
+class File(State):
1057
+	def __init__(self,FSM):
1058
+		super(File, self).__init__(FSM)    
1059
+		
1060
+	def Enter(self):
1061
+		self.FSM.stateLife = 1
1062
+		o = self.FSM.owner 
1063
+		o.header_text = "File"
1064
+		o.pub.register("beat", self)
1065
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
1066
+			pass
1067
+		else:
1068
+			draw_header(o)
1069
+			o.disp.image(o.image, o.rotation)
1070
+		super(File, self).Enter()        
1071
+		
1072
+	def Execute(self):
1073
+		o = self.FSM.owner 
1074
+		menu1_actions(self, o)
1075
+		if o.keyState[16] == 1:
1076
+			draw_menu1(o)
1077
+			o.disp.image(o.image, o.rotation)
1078
+		elif o.keyState[16] == 4:
1079
+			draw_header(o)
1080
+			#self.draw_square()
1081
+			o.disp.image(o.image, o.rotation)
1082
+
1083
+	def ReceiveMessage(self, message):
1084
+		_id = 0
1085
+		o = self.FSM.owner 
1086
+		play_seq(o, message)
1087
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
1088
+			pass
1089
+		else:
1090
+			draw_header(o)            
1091
+			#self.draw_square()  
1092
+		
1093
+		o.disp.image(o.image, o.rotation)
1094
+		# for s in o.soundSlots:
1095
+		#     if s.notes[message[0]][message[1]][0] == 1:
1096
+		#         o.soundSlots[_id].play()
1097
+		#     _id += 1 
1098
+
1099
+	def Exit(self):
1100
+		pass
1101
+
1102
+class Util(State):
1103
+	def __init__(self,FSM):
1104
+		super(Util, self).__init__(FSM)    
1105
+		
1106
+	def Enter(self):
1107
+		o = self.FSM.owner 
1108
+		o.header_text = "Utilities"
1109
+		o.pub.register("beat", self)
1110
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
1111
+			pass
1112
+		else:
1113
+			draw_header(o)
1114
+			o.disp.image(o.image, o.rotation)
1115
+		super(Util, self).Enter()        
1116
+		
1117
+	def Execute(self):
1118
+		o = self.FSM.owner 
1119
+		menu1_actions(self, o)
1120
+		if o.keyState[16] == 1:
1121
+			draw_menu1(o)
1122
+			o.disp.image(o.image, o.rotation)
1123
+		elif o.keyState[16] == 4:
1124
+			draw_header(o)
1125
+			#self.draw_square()
1126
+			o.disp.image(o.image, o.rotation)
1127
+
1128
+		#self.FSM.ToTransition('toLand')
1129
+		
1130
+	def ReceiveMessage(self, message):
1131
+		_id = 0
1132
+		o = self.FSM.owner 
1133
+		play_seq(o, message)
1134
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
1135
+			pass
1136
+		else:
1137
+			draw_header(o)           
1138
+			#self.draw_square()  
1139
+		
1140
+		o.disp.image(o.image, o.rotation)
1141
+		#play_seq(o, message)
1142
+		# for s in o.soundSlots:
1143
+		#     if s.notes[message[0]][message[1]][0] == 1:
1144
+		#         o.soundSlots[_id].play()
1145
+			#id += 1 
1146
+	def Exit(self):
1147
+		pass        
1148
+
1149
+
1150
+
1151
+class Ball(State):
1152
+	def __init__(self,FSM):
1153
+		super(Ball, self).__init__(FSM)    
1154
+		
1155
+	def Enter(self):
1156
+		self.ball_size = 10
1157
+		self.ballx = self.ball_size
1158
+		self.bally = self.ball_size
1159
+		
1160
+		self.ball_speed = 15
1161
+		self.ball_x_dir = 1
1162
+		self.ball_y_dir = 1
1163
+		
1164
+		self.ball2x = self.ball_size + 180
1165
+		self.ball2y = self.ball_size + 70
1166
+	
1167
+		self.ball2_x_dir = -1
1168
+		self.ball2_y_dir = -1
1169
+
1170
+		o = self.FSM.owner 
1171
+		o.header_text = "Utilities"
1172
+		o.pub.register("beat", self)
1173
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
1174
+			pass
1175
+		else:
1176
+			draw_header(o)
1177
+			o.disp.image(o.image, o.rotation)
1178
+		super(Ball, self).Enter()        
1179
+		
1180
+	def Execute(self):
1181
+		o = self.FSM.owner 
1182
+		menu1_actions(self, o)
1183
+		if o.keyState[16] == 1:
1184
+			draw_menu1(o)
1185
+			o.disp.image(o.image, o.rotation)
1186
+		else:
1187
+			now = datetime.now()
1188
+			current_time = time.strftime("%-I:%M %p")
1189
+			#current_time = now.strftime("%H:%M:%S")
1190
+			#print("Current Time =", current_time)
1191
+
1192
+
1193
+			if self.ballx > (o.width - self.ball_size) or self.ballx < (0 + self.ball_size):
1194
+				self.ball_x_dir *= -1
1195
+			if (self.bally > o.height - self.ball_size) or (self.bally < 0 + self.ball_size):
1196
+				self.ball_y_dir *= -1
1197
+
1198
+			self.ballx += self.ball_speed * self.ball_x_dir
1199
+			self.bally += self.ball_speed * self.ball_y_dir
1200
+
1201
+
1202
+			if self.ball2x > (o.width - self.ball_size) or self.ball2x < (0 + self.ball_size):
1203
+				self.ball2_x_dir *= -1
1204
+			if (self.ball2y > o.height - self.ball_size) or (self.ball2y < 0 + self.ball_size):
1205
+				self.ball2_y_dir *= -1
1206
+
1207
+			self.ball2x += self.ball_speed * self.ball2_x_dir
1208
+			self.ball2y += self.ball_speed * self.ball2_y_dir
1209
+
1210
+			o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.blue)
1211
+			
1212
+			o.draw.ellipse((self.ballx - self.ball_size, self.bally - self.ball_size, self.ballx + self.ball_size, self.bally + self.ball_size), fill = o.light_grey, outline =o.light_grey)
1213
+			o.draw.ellipse((self.ball2x - self.ball_size, self.ball2y - self.ball_size, self.ball2x + self.ball_size, self.ball2y + self.ball_size), fill = o.light_grey, outline =o.light_grey)
1214
+			o.center_block(current_time, o.h3, [0,0,o.width,o.height], o.pink)
1215
+			o.disp.image(o.image, o.rotation)
1216
+		
1217
+	def ReceiveMessage(self, message):
1218
+		o = self.FSM.owner 
1219
+		play_seq(o, message)
1220
+		
1221
+	def Exit(self):
1222
+		pass        
1223
+
1224
+#==================================== 
1225
+
1226
+
1227
+class Clock(State):
1228
+	def __init__(self,FSM):
1229
+		super(Clock, self).__init__(FSM)    
1230
+		
1231
+	def Enter(self):
1232
+		o = self.FSM.owner 
1233
+		current_time = time.strftime("%-I:%M:%S")
1234
+		#current_time = time.strftime("%-I:%M:%S %p")
1235
+
1236
+		#self.ball_sizex, self.ball_sizey = o.h3.getsize(current_time)
1237
+		self.ball_sizex, self.ball_sizey = o.h3.getsize("18:88:88")
1238
+		self.ball_sizex /= 2
1239
+		self.ball_sizey /= 2
1240
+		self.ballx = o.width / 2 
1241
+		self.bally = o.height / 2
1242
+		
1243
+		self.ball_speed = 1
1244
+		self.ball_x_dir = 1
1245
+		self.ball_y_dir = 1
1246
+		
1247
+		o.header_text = "Utilities"
1248
+		o.pub.register("beat", self)
1249
+		if o.keyState[16] > 0 or o.keyState[17] > 0:
1250
+			pass
1251
+		else:
1252
+			pass
1253
+			#draw_header(o)
1254
+			#o.disp.image(o.image, o.rotation)
1255
+		super(Clock, self).Enter()        
1256
+		
1257
+	def Execute(self):
1258
+		o = self.FSM.owner 
1259
+		menu1_actions(self, o)
1260
+		if o.keyState[16] == 1:
1261
+			draw_menu1(o)
1262
+			o.disp.image(o.image, o.rotation)
1263
+		else:
1264
+			#now = datetime.now()
1265
+			current_time = time.strftime("%-I:%M:%S")
1266
+			#current_time = time.strftime("%-I:%M:%S %p")
1267
+			#current_time = now.strftime("%H:%M:%S")
1268
+			#print("Current Time =", current_time)
1269
+			#self.ball_sizex, self.ball_sizey = o.h3.getsize(current_time)
1270
+			#self.ball_sizex /= 2
1271
+			#self.ball_sizey /= 2
1272
+
1273
+			if self.ballx > (o.width - self.ball_sizex) or self.ballx < (0 + self.ball_sizex):
1274
+				self.ball_x_dir *= -1
1275
+			if (self.bally > o.height - self.ball_sizey) or (self.bally < 0 + self.ball_sizey):
1276
+				self.ball_y_dir *= -1
1277
+
1278
+			self.ballx += self.ball_speed * self.ball_x_dir
1279
+			self.bally += self.ball_speed * self.ball_y_dir
1280
+
1281
+			o.draw.rectangle((0, 0, o.width, o.height), outline=0, fill=o.black)
1282
+			
1283
+			#o.draw.ellipse((self.ballx - self.ball_sizex, self.bally - self.ball_sizey, self.ballx + self.ball_sizex, self.bally + self.ball_sizey), fill = o.light_grey, outline =o.light_grey)
1284
+			
1285
+			o.draw.text((self.ballx - self.ball_sizex, self.bally - self.ball_sizey), current_time, align='center', font=o.h3, fill=o.olive)
1286
+			#o.center_block(current_time, o.h3, [0,0,o.width,o.height], o.pink)
1287
+			o.disp.image(o.image, o.rotation)
1288
+		
1289
+	def ReceiveMessage(self, message):
1290
+		o = self.FSM.owner 
1291
+		play_seq(o, message)
1292
+		
1293
+	def Exit(self):
1294
+		pass        
1295
+
1296
+
1297
+#==================================== 
1298
+#main
1299
+#select pattern
1300
+#pattern
1301
+#song

+ 0
- 0
lib/__init__.py View File


+ 2484
- 0
lib/configobj.py View File

@@ -0,0 +1,2484 @@
1
+# configobj.py
2
+# A config file reader/writer that supports nested sections in config files.
3
+# Copyright (C) 2005-2014:
4
+# (name) : (email)
5
+# Michael Foord: fuzzyman AT voidspace DOT org DOT uk
6
+# Nicola Larosa: nico AT tekNico DOT net
7
+# Rob Dennis: rdennis AT gmail DOT com
8
+# Eli Courtwright: eli AT courtwright DOT org
9
+
10
+# This software is licensed under the terms of the BSD license.
11
+# http://opensource.org/licenses/BSD-3-Clause
12
+
13
+# ConfigObj 5 - main repository for documentation and issue tracking:
14
+# https://github.com/DiffSK/configobj
15
+
16
+import os
17
+import re
18
+import sys
19
+
20
+from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
21
+
22
+#from six import six
23
+import six
24
+#from _version import __version__
25
+
26
+# imported lazily to avoid startup performance hit if it isn't used
27
+compiler = None
28
+
29
+# A dictionary mapping BOM to
30
+# the encoding to decode with, and what to set the
31
+# encoding attribute to.
32
+BOMS = {
33
+    BOM_UTF8: ('utf_8', None),
34
+    BOM_UTF16_BE: ('utf16_be', 'utf_16'),
35
+    BOM_UTF16_LE: ('utf16_le', 'utf_16'),
36
+    BOM_UTF16: ('utf_16', 'utf_16'),
37
+    }
38
+# All legal variants of the BOM codecs.
39
+# TODO: the list of aliases is not meant to be exhaustive, is there a
40
+#   better way ?
41
+BOM_LIST = {
42
+    'utf_16': 'utf_16',
43
+    'u16': 'utf_16',
44
+    'utf16': 'utf_16',
45
+    'utf-16': 'utf_16',
46
+    'utf16_be': 'utf16_be',
47
+    'utf_16_be': 'utf16_be',
48
+    'utf-16be': 'utf16_be',
49
+    'utf16_le': 'utf16_le',
50
+    'utf_16_le': 'utf16_le',
51
+    'utf-16le': 'utf16_le',
52
+    'utf_8': 'utf_8',
53
+    'u8': 'utf_8',
54
+    'utf': 'utf_8',
55
+    'utf8': 'utf_8',
56
+    'utf-8': 'utf_8',
57
+    }
58
+
59
+# Map of encodings to the BOM to write.
60
+BOM_SET = {
61
+    'utf_8': BOM_UTF8,
62
+    'utf_16': BOM_UTF16,
63
+    'utf16_be': BOM_UTF16_BE,
64
+    'utf16_le': BOM_UTF16_LE,
65
+    None: BOM_UTF8
66
+    }
67
+
68
+
69
+def match_utf8(encoding):
70
+    return BOM_LIST.get(encoding.lower()) == 'utf_8'
71
+
72
+
73
+# Quote strings used for writing values
74
+squot = "'%s'"
75
+dquot = '"%s"'
76
+noquot = "%s"
77
+wspace_plus = ' \r\n\v\t\'"'
78
+tsquot = '"""%s"""'
79
+tdquot = "'''%s'''"
80
+
81
+# Sentinel for use in getattr calls to replace hasattr
82
+MISSING = object()
83
+
84
+__all__ = (
85
+    'DEFAULT_INDENT_TYPE',
86
+    'DEFAULT_INTERPOLATION',
87
+    'ConfigObjError',
88
+    'NestingError',
89
+    'ParseError',
90
+    'DuplicateError',
91
+    'ConfigspecError',
92
+    'ConfigObj',
93
+    'SimpleVal',
94
+    'InterpolationError',
95
+    'InterpolationLoopError',
96
+    'MissingInterpolationOption',
97
+    'RepeatSectionError',
98
+    'ReloadError',
99
+    'UnreprError',
100
+    'UnknownType',
101
+    'flatten_errors',
102
+    'get_extra_values'
103
+)
104
+
105
+DEFAULT_INTERPOLATION = 'configparser'
106
+DEFAULT_INDENT_TYPE = '    '
107
+MAX_INTERPOL_DEPTH = 10
108
+
109
+OPTION_DEFAULTS = {
110
+    'interpolation': True,
111
+    'raise_errors': False,
112
+    'list_values': True,
113
+    'create_empty': False,
114
+    'file_error': False,
115
+    'configspec': None,
116
+    'stringify': True,
117
+    # option may be set to one of ('', ' ', '\t')
118
+    'indent_type': None,
119
+    'encoding': None,
120
+    'default_encoding': None,
121
+    'unrepr': False,
122
+    'write_empty_values': False,
123
+}
124
+
125
+# this could be replaced if six is used for compatibility, or there are no
126
+# more assertions about items being a string
127
+
128
+
129
+def getObj(s):
130
+    global compiler
131
+    if compiler is None:
132
+        import compiler
133
+    s = "a=" + s
134
+    p = compiler.parse(s)
135
+    return p.getChildren()[1].getChildren()[0].getChildren()[1]
136
+
137
+
138
+class UnknownType(Exception):
139
+    pass
140
+
141
+
142
+class Builder(object):
143
+    
144
+    def build(self, o):
145
+        if m is None:
146
+            raise UnknownType(o.__class__.__name__)
147
+        return m(o)
148
+    
149
+    def build_List(self, o):
150
+        return list(map(self.build, o.getChildren()))
151
+    
152
+    def build_Const(self, o):
153
+        return o.value
154
+    
155
+    def build_Dict(self, o):
156
+        d = {}
157
+        i = iter(map(self.build, o.getChildren()))
158
+        for el in i:
159
+            d[el] = next(i)
160
+        return d
161
+    
162
+    def build_Tuple(self, o):
163
+        return tuple(self.build_List(o))
164
+    
165
+    def build_Name(self, o):
166
+        if o.name == 'None':
167
+            return None
168
+        if o.name == 'True':
169
+            return True
170
+        if o.name == 'False':
171
+            return False
172
+        
173
+        # An undefined Name
174
+        raise UnknownType('Undefined Name')
175
+    
176
+    def build_Add(self, o):
177
+        real, imag = list(map(self.build_Const, o.getChildren()))
178
+        try:
179
+            real = float(real)
180
+        except TypeError:
181
+            raise UnknownType('Add')
182
+        if not isinstance(imag, complex) or imag.real != 0.0:
183
+            raise UnknownType('Add')
184
+        return real+imag
185
+    
186
+    def build_Getattr(self, o):
187
+        parent = self.build(o.expr)
188
+        return getattr(parent, o.attrname)
189
+    
190
+    def build_UnarySub(self, o):
191
+        return -self.build_Const(o.getChildren()[0])
192
+    
193
+    def build_UnaryAdd(self, o):
194
+        return self.build_Const(o.getChildren()[0])
195
+
196
+
197
+_builder = Builder()
198
+
199
+
200
+def unrepr(s):
201
+    if not s:
202
+        return s
203
+    
204
+    # this is supposed to be safe
205
+    import ast
206
+    return ast.literal_eval(s)
207
+
208
+
209
+class ConfigObjError(SyntaxError):
210
+    """
211
+    This is the base class for all errors that ConfigObj raises.
212
+    It is a subclass of SyntaxError.
213
+    """
214
+    def __init__(self, message='', line_number=None, line=''):
215
+        self.line = line
216
+        self.line_number = line_number
217
+        SyntaxError.__init__(self, message)
218
+
219
+
220
+class NestingError(ConfigObjError):
221
+    """
222
+    This error indicates a level of nesting that doesn't match.
223
+    """
224
+
225
+
226
+class ParseError(ConfigObjError):
227
+    """
228
+    This error indicates that a line is badly written.
229
+    It is neither a valid ``key = value`` line,
230
+    nor a valid section marker line.
231
+    """
232
+
233
+
234
+class ReloadError(IOError):
235
+    """
236
+    A 'reload' operation failed.
237
+    This exception is a subclass of ``IOError``.
238
+    """
239
+    def __init__(self):
240
+        IOError.__init__(self, 'reload failed, filename is not set.')
241
+
242
+
243
+class DuplicateError(ConfigObjError):
244
+    """
245
+    The keyword or section specified already exists.
246
+    """
247
+
248
+
249
+class ConfigspecError(ConfigObjError):
250
+    """
251
+    An error occured whilst parsing a configspec.
252
+    """
253
+
254
+
255
+class InterpolationError(ConfigObjError):
256
+    """Base class for the two interpolation errors."""
257
+
258
+
259
+class InterpolationLoopError(InterpolationError):
260
+    """Maximum interpolation depth exceeded in string interpolation."""
261
+
262
+    def __init__(self, option):
263
+        InterpolationError.__init__(
264
+            self,
265
+            'interpolation loop detected in value "%s".' % option)
266
+
267
+
268
+class RepeatSectionError(ConfigObjError):
269
+    """
270
+    This error indicates additional sections in a section with a
271
+    ``__many__`` (repeated) section.
272
+    """
273
+
274
+
275
+class MissingInterpolationOption(InterpolationError):
276
+    """A value specified for interpolation was missing."""
277
+    def __init__(self, option):
278
+        msg = 'missing option "%s" in interpolation.' % option
279
+        InterpolationError.__init__(self, msg)
280
+
281
+
282
+class UnreprError(ConfigObjError):
283
+    """An error parsing in unrepr mode."""
284
+
285
+
286
+
287
+class InterpolationEngine(object):
288
+    """
289
+    A helper class to help perform string interpolation.
290
+
291
+    This class is an abstract base class; its descendants perform
292
+    the actual work.
293
+    """
294
+
295
+    # compiled regexp to use in self.interpolate()
296
+    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
297
+    _cookie = '%'
298
+
299
+    def __init__(self, section):
300
+        # the Section instance that "owns" this engine
301
+        self.section = section
302
+
303
+
304
+    def interpolate(self, key, value):
305
+        # short-cut
306
+        if not self._cookie in value:
307
+            return value
308
+        
309
+        def recursive_interpolate(key, value, section, backtrail):
310
+            """The function that does the actual work.
311
+
312
+            ``value``: the string we're trying to interpolate.
313
+            ``section``: the section in which that string was found
314
+            ``backtrail``: a dict to keep track of where we've been,
315
+            to detect and prevent infinite recursion loops
316
+
317
+            This is similar to a depth-first-search algorithm.
318
+            """
319
+            # Have we been here already?
320
+            if (key, section.name) in backtrail:
321
+                # Yes - infinite loop detected
322
+                raise InterpolationLoopError(key)
323
+            # Place a marker on our backtrail so we won't come back here again
324
+            backtrail[(key, section.name)] = 1
325
+
326
+            # Now start the actual work
327
+            match = self._KEYCRE.search(value)
328
+            while match:
329
+                # The actual parsing of the match is implementation-dependent,
330
+                # so delegate to our helper function
331
+                k, v, s = self._parse_match(match)
332
+                if k is None:
333
+                    # That's the signal that no further interpolation is needed
334
+                    replacement = v
335
+                else:
336
+                    # Further interpolation may be needed to obtain final value
337
+                    replacement = recursive_interpolate(k, v, s, backtrail)
338
+                # Replace the matched string with its final value
339
+                start, end = match.span()
340
+                value = ''.join((value[:start], replacement, value[end:]))
341
+                new_search_start = start + len(replacement)
342
+                # Pick up the next interpolation key, if any, for next time
343
+                # through the while loop
344
+                match = self._KEYCRE.search(value, new_search_start)
345
+
346
+            # Now safe to come back here again; remove marker from backtrail
347
+            del backtrail[(key, section.name)]
348
+
349
+            return value
350
+
351
+        # Back in interpolate(), all we have to do is kick off the recursive
352
+        # function with appropriate starting values
353
+        value = recursive_interpolate(key, value, self.section, {})
354
+        return value
355
+
356
+
357
+    def _fetch(self, key):
358
+        """Helper function to fetch values from owning section.
359
+
360
+        Returns a 2-tuple: the value, and the section where it was found.
361
+        """
362
+        # switch off interpolation before we try and fetch anything !
363
+        save_interp = self.section.main.interpolation
364
+        self.section.main.interpolation = False
365
+
366
+        # Start at section that "owns" this InterpolationEngine
367
+        current_section = self.section
368
+        while True:
369
+            # try the current section first
370
+            val = current_section.get(key)
371
+            if val is not None and not isinstance(val, Section):
372
+                break
373
+            # try "DEFAULT" next
374
+            val = current_section.get('DEFAULT', {}).get(key)
375
+            if val is not None and not isinstance(val, Section):
376
+                break
377
+            # move up to parent and try again
378
+            # top-level's parent is itself
379
+            if current_section.parent is current_section:
380
+                # reached top level, time to give up
381
+                break
382
+            current_section = current_section.parent
383
+
384
+        # restore interpolation to previous value before returning
385
+        self.section.main.interpolation = save_interp
386
+        if val is None:
387
+            raise MissingInterpolationOption(key)
388
+        return val, current_section
389
+
390
+
391
+    def _parse_match(self, match):
392
+        """Implementation-dependent helper function.
393
+
394
+        Will be passed a match object corresponding to the interpolation
395
+        key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
396
+        key in the appropriate config file section (using the ``_fetch()``
397
+        helper function) and return a 3-tuple: (key, value, section)
398
+
399
+        ``key`` is the name of the key we're looking for
400
+        ``value`` is the value found for that key
401
+        ``section`` is a reference to the section where it was found
402
+
403
+        ``key`` and ``section`` should be None if no further
404
+        interpolation should be performed on the resulting value
405
+        (e.g., if we interpolated "$$" and returned "$").
406
+        """
407
+        raise NotImplementedError()
408
+    
409
+
410
+
411
+class ConfigParserInterpolation(InterpolationEngine):
412
+    """Behaves like ConfigParser."""
413
+    _cookie = '%'
414
+    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
415
+
416
+    def _parse_match(self, match):
417
+        key = match.group(1)
418
+        value, section = self._fetch(key)
419
+        return key, value, section
420
+
421
+
422
+
423
+class TemplateInterpolation(InterpolationEngine):
424
+    """Behaves like string.Template."""
425
+    _cookie = '$'
426
+    _delimiter = '$'
427
+    _KEYCRE = re.compile(r"""
428
+        \$(?:
429
+          (?P<escaped>\$)              |   # Two $ signs
430
+          (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
431
+          {(?P<braced>[^}]*)}              # ${name} format
432
+        )
433
+        """, re.IGNORECASE | re.VERBOSE)
434
+
435
+    def _parse_match(self, match):
436
+        # Valid name (in or out of braces): fetch value from section
437
+        key = match.group('named') or match.group('braced')
438
+        if key is not None:
439
+            value, section = self._fetch(key)
440
+            return key, value, section
441
+        # Escaped delimiter (e.g., $$): return single delimiter
442
+        if match.group('escaped') is not None:
443
+            # Return None for key and section to indicate it's time to stop
444
+            return None, self._delimiter, None
445
+        # Anything else: ignore completely, just return it unchanged
446
+        return None, match.group(), None
447
+
448
+
449
+interpolation_engines = {
450
+    'configparser': ConfigParserInterpolation,
451
+    'template': TemplateInterpolation,
452
+}
453
+
454
+
455
+def __newobj__(cls, *args):
456
+    # Hack for pickle
457
+    return cls.__new__(cls, *args) 
458
+
459
+class Section(dict):
460
+    """
461
+    A dictionary-like object that represents a section in a config file.
462
+    
463
+    It does string interpolation if the 'interpolation' attribute
464
+    of the 'main' object is set to True.
465
+    
466
+    Interpolation is tried first from this object, then from the 'DEFAULT'
467
+    section of this object, next from the parent and its 'DEFAULT' section,
468
+    and so on until the main object is reached.
469
+    
470
+    A Section will behave like an ordered dictionary - following the
471
+    order of the ``scalars`` and ``sections`` attributes.
472
+    You can use this to change the order of members.
473
+    
474
+    Iteration follows the order: scalars, then sections.
475
+    """
476
+
477
+    
478
+    def __setstate__(self, state):
479
+        dict.update(self, state[0])
480
+        self.__dict__.update(state[1])
481
+
482
+    def __reduce__(self):
483
+        state = (dict(self), self.__dict__)
484
+        return (__newobj__, (self.__class__,), state)
485
+    
486
+    
487
+    def __init__(self, parent, depth, main, indict=None, name=None):
488
+        """
489
+        * parent is the section above
490
+        * depth is the depth level of this section
491
+        * main is the main ConfigObj
492
+        * indict is a dictionary to initialise the section with
493
+        """
494
+        if indict is None:
495
+            indict = {}
496
+        dict.__init__(self)
497
+        # used for nesting level *and* interpolation
498
+        self.parent = parent
499
+        # used for the interpolation attribute
500
+        self.main = main
501
+        # level of nesting depth of this Section
502
+        self.depth = depth
503
+        # purely for information
504
+        self.name = name
505
+        #
506
+        self._initialise()
507
+        # we do this explicitly so that __setitem__ is used properly
508
+        # (rather than just passing to ``dict.__init__``)
509
+        for entry, value in indict.items():
510
+            self[entry] = value
511
+            
512
+            
513
+    def _initialise(self):
514
+        # the sequence of scalar values in this Section
515
+        self.scalars = []
516
+        # the sequence of sections in this Section
517
+        self.sections = []
518
+        # for comments :-)
519
+        self.comments = {}
520
+        self.inline_comments = {}
521
+        # the configspec
522
+        self.configspec = None
523
+        # for defaults
524
+        self.defaults = []
525
+        self.default_values = {}
526
+        self.extra_values = []
527
+        self._created = False
528
+
529
+
530
+    def _interpolate(self, key, value):
531
+        try:
532
+            # do we already have an interpolation engine?
533
+            engine = self._interpolation_engine
534
+        except AttributeError:
535
+            # not yet: first time running _interpolate(), so pick the engine
536
+            name = self.main.interpolation
537
+            if name == True:  # note that "if name:" would be incorrect here
538
+                # backwards-compatibility: interpolation=True means use default
539
+                name = DEFAULT_INTERPOLATION
540
+            name = name.lower()  # so that "Template", "template", etc. all work
541
+            class_ = interpolation_engines.get(name, None)
542
+            if class_ is None:
543
+                # invalid value for self.main.interpolation
544
+                self.main.interpolation = False
545
+                return value
546
+            else:
547
+                # save reference to engine so we don't have to do this again
548
+                engine = self._interpolation_engine = class_(self)
549
+        # let the engine do the actual work
550
+        return engine.interpolate(key, value)
551
+
552
+
553
+    def __getitem__(self, key):
554
+        """Fetch the item and do string interpolation."""
555
+        val = dict.__getitem__(self, key)
556
+        if self.main.interpolation: 
557
+            if isinstance(val, six.string_types):
558
+                return self._interpolate(key, val)
559
+            if isinstance(val, list):
560
+                def _check(entry):
561
+                    if isinstance(entry, six.string_types):
562
+                        return self._interpolate(key, entry)
563
+                    return entry
564
+                new = [_check(entry) for entry in val]
565
+                if new != val:
566
+                    return new
567
+        return val
568
+
569
+
570
+    def __setitem__(self, key, value, unrepr=False):
571
+        """
572
+        Correctly set a value.
573
+        
574
+        Making dictionary values Section instances.
575
+        (We have to special case 'Section' instances - which are also dicts)
576
+        
577
+        Keys must be strings.
578
+        Values need only be strings (or lists of strings) if
579
+        ``main.stringify`` is set.
580
+        
581
+        ``unrepr`` must be set when setting a value to a dictionary, without
582
+        creating a new sub-section.
583
+        """
584
+        if not isinstance(key, six.string_types):
585
+            raise ValueError('The key "%s" is not a string.' % key)
586
+        
587
+        # add the comment
588
+        if key not in self.comments:
589
+            self.comments[key] = []
590
+            self.inline_comments[key] = ''
591
+        # remove the entry from defaults
592
+        if key in self.defaults:
593
+            self.defaults.remove(key)
594
+        #
595
+        if isinstance(value, Section):
596
+            if key not in self:
597
+                self.sections.append(key)
598
+            dict.__setitem__(self, key, value)
599
+        elif isinstance(value, dict) and not unrepr:
600
+            # First create the new depth level,
601
+            # then create the section
602
+            if key not in self:
603
+                self.sections.append(key)
604
+            new_depth = self.depth + 1
605
+            dict.__setitem__(
606
+                self,
607
+                key,
608
+                Section(
609
+                    self,
610
+                    new_depth,
611
+                    self.main,
612
+                    indict=value,
613
+                    name=key))
614
+        else:
615
+            if key not in self:
616
+                self.scalars.append(key)
617
+            if not self.main.stringify:
618
+                if isinstance(value, six.string_types):
619
+                    pass
620
+                elif isinstance(value, (list, tuple)):
621
+                    for entry in value:
622
+                        if not isinstance(entry, six.string_types):
623
+                            raise TypeError('Value is not a string "%s".' % entry)
624
+                else:
625
+                    raise TypeError('Value is not a string "%s".' % value)
626
+            dict.__setitem__(self, key, value)
627
+
628
+
629
+    def __delitem__(self, key):
630
+        """Remove items from the sequence when deleting."""
631
+        dict. __delitem__(self, key)
632
+        if key in self.scalars:
633
+            self.scalars.remove(key)
634
+        else:
635
+            self.sections.remove(key)
636
+        del self.comments[key]
637
+        del self.inline_comments[key]
638
+
639
+
640
+    def get(self, key, default=None):
641
+        """A version of ``get`` that doesn't bypass string interpolation."""
642
+        try:
643
+            return self[key]
644
+        except KeyError:
645
+            return default
646
+
647
+
648
+    def update(self, indict):
649
+        """
650
+        A version of update that uses our ``__setitem__``.
651
+        """
652
+        for entry in indict:
653
+            self[entry] = indict[entry]
654
+
655
+
656
+    def pop(self, key, default=MISSING):
657
+        """
658
+        'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
659
+        If key is not found, d is returned if given, otherwise KeyError is raised'
660
+        """
661
+        try:
662
+            val = self[key]
663
+        except KeyError:
664
+            if default is MISSING:
665
+                raise
666
+            val = default
667
+        else:
668
+            del self[key]
669
+        return val
670
+
671
+
672
+    def popitem(self):
673
+        """Pops the first (key,val)"""
674
+        sequence = (self.scalars + self.sections)
675
+        if not sequence:
676
+            raise KeyError(": 'popitem(): dictionary is empty'")
677
+        key = sequence[0]
678
+        val =  self[key]
679
+        del self[key]
680
+        return key, val
681
+
682
+
683
+    def clear(self):
684
+        """
685
+        A version of clear that also affects scalars/sections
686
+        Also clears comments and configspec.
687
+        
688
+        Leaves other attributes alone :
689
+            depth/main/parent are not affected
690
+        """
691
+        dict.clear(self)
692
+        self.scalars = []
693
+        self.sections = []
694
+        self.comments = {}
695
+        self.inline_comments = {}
696
+        self.configspec = None
697
+        self.defaults = []
698
+        self.extra_values = []
699
+
700
+
701
+    def setdefault(self, key, default=None):
702
+        """A version of setdefault that sets sequence if appropriate."""
703
+        try:
704
+            return self[key]
705
+        except KeyError:
706
+            self[key] = default
707
+            return self[key]
708
+
709
+
710
+    def items(self):
711
+        """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
712
+        return list(zip((self.scalars + self.sections), list(self.values())))
713
+
714
+
715
+    def keys(self):
716
+        """D.keys() -> list of D's keys"""
717
+        return (self.scalars + self.sections)
718
+
719
+
720
+    def values(self):
721
+        """D.values() -> list of D's values"""
722
+        return [self[key] for key in (self.scalars + self.sections)]
723
+
724
+
725
+    def iteritems(self):
726
+        """D.iteritems() -> an iterator over the (key, value) items of D"""
727
+        return iter(list(self.items()))
728
+
729
+
730
+    def iterkeys(self):
731
+        """D.iterkeys() -> an iterator over the keys of D"""
732
+        return iter((self.scalars + self.sections))
733
+
734
+    __iter__ = iterkeys
735
+
736
+
737
+    def itervalues(self):
738
+        """D.itervalues() -> an iterator over the values of D"""
739
+        return iter(list(self.values()))
740
+
741
+
742
+    def __repr__(self):
743
+        """x.__repr__() <==> repr(x)"""
744
+        def _getval(key):
745
+            try:
746
+                return self[key]
747
+            except MissingInterpolationOption:
748
+                return dict.__getitem__(self, key)
749
+        return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key))))
750
+            for key in (self.scalars + self.sections)])
751
+
752
+    __str__ = __repr__
753
+    __str__.__doc__ = "x.__str__() <==> str(x)"
754
+
755
+
756
+    # Extra methods - not in a normal dictionary
757
+
758
+    def dict(self):
759
+        """
760
+        Return a deepcopy of self as a dictionary.
761
+        
762
+        All members that are ``Section`` instances are recursively turned to
763
+        ordinary dictionaries - by calling their ``dict`` method.
764
+        
765
+        >>> n = a.dict()
766
+        >>> n == a
767
+        1
768
+        >>> n is a
769
+        0
770
+        """
771
+        newdict = {}
772
+        for entry in self:
773
+            this_entry = self[entry]
774
+            if isinstance(this_entry, Section):
775
+                this_entry = this_entry.dict()
776
+            elif isinstance(this_entry, list):
777
+                # create a copy rather than a reference
778
+                this_entry = list(this_entry)
779
+            elif isinstance(this_entry, tuple):
780
+                # create a copy rather than a reference
781
+                this_entry = tuple(this_entry)
782
+            newdict[entry] = this_entry
783
+        return newdict
784
+
785
+
786
+    def merge(self, indict):
787
+        """
788
+        A recursive update - useful for merging config files.
789
+        
790
+        >>> a = '''[section1]
791
+        ...     option1 = True
792
+        ...     [[subsection]]
793
+        ...     more_options = False
794
+        ...     # end of file'''.splitlines()
795
+        >>> b = '''# File is user.ini
796
+        ...     [section1]
797
+        ...     option1 = False
798
+        ...     # end of file'''.splitlines()
799
+        >>> c1 = ConfigObj(b)
800
+        >>> c2 = ConfigObj(a)
801
+        >>> c2.merge(c1)
802
+        >>> c2
803
+        ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
804
+        """
805
+        for key, val in list(indict.items()):
806
+            if (key in self and isinstance(self[key], dict) and
807
+                                isinstance(val, dict)):
808
+                self[key].merge(val)
809
+            else:   
810
+                self[key] = val
811
+
812
+
813
+    def rename(self, oldkey, newkey):
814
+        """
815
+        Change a keyname to another, without changing position in sequence.
816
+        
817
+        Implemented so that transformations can be made on keys,
818
+        as well as on values. (used by encode and decode)
819
+        
820
+        Also renames comments.
821
+        """
822
+        if oldkey in self.scalars:
823
+            the_list = self.scalars
824
+        elif oldkey in self.sections:
825
+            the_list = self.sections
826
+        else:
827
+            raise KeyError('Key "%s" not found.' % oldkey)
828
+        pos = the_list.index(oldkey)
829
+        #
830
+        val = self[oldkey]
831
+        dict.__delitem__(self, oldkey)
832
+        dict.__setitem__(self, newkey, val)
833
+        the_list.remove(oldkey)
834
+        the_list.insert(pos, newkey)
835
+        comm = self.comments[oldkey]
836
+        inline_comment = self.inline_comments[oldkey]
837
+        del self.comments[oldkey]
838
+        del self.inline_comments[oldkey]
839
+        self.comments[newkey] = comm
840
+        self.inline_comments[newkey] = inline_comment
841
+
842
+
843
+    def walk(self, function, raise_errors=True,
844
+            call_on_sections=False, **keywargs):
845
+        """
846
+        Walk every member and call a function on the keyword and value.
847
+        
848
+        Return a dictionary of the return values
849
+        
850
+        If the function raises an exception, raise the errror
851
+        unless ``raise_errors=False``, in which case set the return value to
852
+        ``False``.
853
+        
854
+        Any unrecognised keyword arguments you pass to walk, will be pased on
855
+        to the function you pass in.
856
+        
857
+        Note: if ``call_on_sections`` is ``True`` then - on encountering a
858
+        subsection, *first* the function is called for the *whole* subsection,
859
+        and then recurses into it's members. This means your function must be
860
+        able to handle strings, dictionaries and lists. This allows you
861
+        to change the key of subsections as well as for ordinary members. The
862
+        return value when called on the whole subsection has to be discarded.
863
+        
864
+        See  the encode and decode methods for examples, including functions.
865
+        
866
+        .. admonition:: caution
867
+        
868
+            You can use ``walk`` to transform the names of members of a section
869
+            but you mustn't add or delete members.
870
+        
871
+        >>> config = '''[XXXXsection]
872
+        ... XXXXkey = XXXXvalue'''.splitlines()
873
+        >>> cfg = ConfigObj(config)
874
+        >>> cfg
875
+        ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}})
876
+        >>> def transform(section, key):
877
+        ...     val = section[key]
878
+        ...     newkey = key.replace('XXXX', 'CLIENT1')
879
+        ...     section.rename(key, newkey)
880
+        ...     if isinstance(val, (tuple, list, dict)):
881
+        ...         pass
882
+        ...     else:
883
+        ...         val = val.replace('XXXX', 'CLIENT1')
884
+        ...         section[newkey] = val
885
+        >>> cfg.walk(transform, call_on_sections=True)
886
+        {'CLIENT1section': {'CLIENT1key': None}}
887
+        >>> cfg
888
+        ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
889
+        """
890
+        out = {}
891
+        # scalars first
892
+        for i in range(len(self.scalars)):
893
+            entry = self.scalars[i]
894
+            try:
895
+                val = function(self, entry, **keywargs)
896
+                # bound again in case name has changed
897
+                entry = self.scalars[i]
898
+                out[entry] = val
899
+            except Exception:
900
+                if raise_errors:
901
+                    raise
902
+                else:
903
+                    entry = self.scalars[i]
904
+                    out[entry] = False
905
+        # then sections
906
+        for i in range(len(self.sections)):
907
+            entry = self.sections[i]
908
+            if call_on_sections:
909
+                try:
910
+                    function(self, entry, **keywargs)
911
+                except Exception:
912
+                    if raise_errors:
913
+                        raise
914
+                    else:
915
+                        entry = self.sections[i]
916
+                        out[entry] = False
917
+                # bound again in case name has changed
918
+                entry = self.sections[i]
919
+            # previous result is discarded
920
+            out[entry] = self[entry].walk(
921
+                function,
922
+                raise_errors=raise_errors,
923
+                call_on_sections=call_on_sections,
924
+                **keywargs)
925
+        return out
926
+
927
+
928
+    def as_bool(self, key):
929
+        """
930
+        Accepts a key as input. The corresponding value must be a string or
931
+        the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
932
+        retain compatibility with Python 2.2.
933
+        
934
+        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
935
+        ``True``.
936
+        
937
+        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
938
+        ``False``.
939
+        
940
+        ``as_bool`` is not case sensitive.
941
+        
942
+        Any other input will raise a ``ValueError``.
943
+        
944
+        >>> a = ConfigObj()
945
+        >>> a['a'] = 'fish'
946
+        >>> a.as_bool('a')
947
+        Traceback (most recent call last):
948
+        ValueError: Value "fish" is neither True nor False
949
+        >>> a['b'] = 'True'
950
+        >>> a.as_bool('b')
951
+        1
952
+        >>> a['b'] = 'off'
953
+        >>> a.as_bool('b')
954
+        0
955
+        """
956
+        val = self[key]
957
+        if val == True:
958
+            return True
959
+        elif val == False:
960
+            return False
961
+        else:
962
+            try:
963
+                if not isinstance(val, six.string_types):
964
+                    # TODO: Why do we raise a KeyError here?
965
+                    raise KeyError()
966
+                else:
967
+                    return self.main._bools[val.lower()]
968
+            except KeyError:
969
+                raise ValueError('Value "%s" is neither True nor False' % val)
970
+
971
+
972
+    def as_int(self, key):
973
+        """
974
+        A convenience method which coerces the specified value to an integer.
975
+        
976
+        If the value is an invalid literal for ``int``, a ``ValueError`` will
977
+        be raised.
978
+        
979
+        >>> a = ConfigObj()
980
+        >>> a['a'] = 'fish'
981
+        >>> a.as_int('a')
982
+        Traceback (most recent call last):
983
+        ValueError: invalid literal for int() with base 10: 'fish'
984
+        >>> a['b'] = '1'
985
+        >>> a.as_int('b')
986
+        1
987
+        >>> a['b'] = '3.2'
988
+        >>> a.as_int('b')
989
+        Traceback (most recent call last):
990
+        ValueError: invalid literal for int() with base 10: '3.2'
991
+        """
992
+        return int(self[key])
993
+
994
+
995
+    def as_float(self, key):
996
+        """
997
+        A convenience method which coerces the specified value to a float.
998
+        
999
+        If the value is an invalid literal for ``float``, a ``ValueError`` will
1000
+        be raised.
1001
+        
1002
+        >>> a = ConfigObj()
1003
+        >>> a['a'] = 'fish'
1004
+        >>> a.as_float('a')  #doctest: +IGNORE_EXCEPTION_DETAIL
1005
+        Traceback (most recent call last):
1006
+        ValueError: invalid literal for float(): fish
1007
+        >>> a['b'] = '1'
1008
+        >>> a.as_float('b')
1009
+        1.0
1010
+        >>> a['b'] = '3.2'
1011
+        >>> a.as_float('b')  #doctest: +ELLIPSIS
1012
+        3.2...
1013
+        """
1014
+        return float(self[key])
1015
+    
1016
+    
1017
+    def as_list(self, key):
1018
+        """
1019
+        A convenience method which fetches the specified value, guaranteeing
1020
+        that it is a list.
1021
+        
1022
+        >>> a = ConfigObj()
1023
+        >>> a['a'] = 1
1024
+        >>> a.as_list('a')
1025
+        [1]
1026
+        >>> a['a'] = (1,)
1027
+        >>> a.as_list('a')
1028
+        [1]
1029
+        >>> a['a'] = [1]
1030
+        >>> a.as_list('a')
1031
+        [1]
1032
+        """
1033
+        result = self[key]
1034
+        if isinstance(result, (tuple, list)):
1035
+            return list(result)
1036
+        return [result]
1037
+        
1038
+
1039
+    def restore_default(self, key):
1040
+        """
1041
+        Restore (and return) default value for the specified key.
1042
+        
1043
+        This method will only work for a ConfigObj that was created
1044
+        with a configspec and has been validated.
1045
+        
1046
+        If there is no default value for this key, ``KeyError`` is raised.
1047
+        """
1048
+        default = self.default_values[key]
1049
+        dict.__setitem__(self, key, default)
1050
+        if key not in self.defaults:
1051
+            self.defaults.append(key)
1052
+        return default
1053
+
1054
+    
1055
+    def restore_defaults(self):
1056
+        """
1057
+        Recursively restore default values to all members
1058
+        that have them.
1059
+        
1060
+        This method will only work for a ConfigObj that was created
1061
+        with a configspec and has been validated.
1062
+        
1063
+        It doesn't delete or modify entries without default values.
1064
+        """
1065
+        for key in self.default_values:
1066
+            self.restore_default(key)
1067
+            
1068
+        for section in self.sections:
1069
+            self[section].restore_defaults()
1070
+
1071
+
1072
+class ConfigObj(Section):
1073
+    """An object to read, create, and write config files."""
1074
+
1075
+    _keyword = re.compile(r'''^ # line start
1076
+        (\s*)                   # indentation
1077
+        (                       # keyword
1078
+            (?:".*?")|          # double quotes
1079
+            (?:'.*?')|          # single quotes
1080
+            (?:[^'"=].*?)       # no quotes
1081
+        )
1082
+        \s*=\s*                 # divider
1083
+        (.*)                    # value (including list values and comments)
1084
+        $   # line end
1085
+        ''',
1086
+        re.VERBOSE)
1087
+
1088
+    _sectionmarker = re.compile(r'''^
1089
+        (\s*)                     # 1: indentation
1090
+        ((?:\[\s*)+)              # 2: section marker open
1091
+        (                         # 3: section name open
1092
+            (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
1093
+            (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
1094
+            (?:[^'"\s].*?)        # at least one non-space unquoted
1095
+        )                         # section name close
1096
+        ((?:\s*\])+)              # 4: section marker close
1097
+        \s*(\#.*)?                # 5: optional comment
1098
+        $''',
1099
+        re.VERBOSE)
1100
+
1101
+    # this regexp pulls list values out as a single string
1102
+    # or single values and comments
1103
+    # FIXME: this regex adds a '' to the end of comma terminated lists
1104
+    #   workaround in ``_handle_value``
1105
+    _valueexp = re.compile(r'''^
1106
+        (?:
1107
+            (?:
1108
+                (
1109
+                    (?:
1110
+                        (?:
1111
+                            (?:".*?")|              # double quotes
1112
+                            (?:'.*?')|              # single quotes
1113
+                            (?:[^'",\#][^,\#]*?)    # unquoted
1114
+                        )
1115
+                        \s*,\s*                     # comma
1116
+                    )*      # match all list items ending in a comma (if any)
1117
+                )
1118
+                (
1119
+                    (?:".*?")|                      # double quotes
1120
+                    (?:'.*?')|                      # single quotes
1121
+                    (?:[^'",\#\s][^,]*?)|           # unquoted
1122
+                    (?:(?<!,))                      # Empty value
1123
+                )?          # last item in a list - or string value
1124
+            )|
1125
+            (,)             # alternatively a single comma - empty list
1126
+        )
1127
+        \s*(\#.*)?          # optional comment
1128
+        $''',
1129
+        re.VERBOSE)
1130
+
1131
+    # use findall to get the members of a list value
1132
+    _listvalueexp = re.compile(r'''
1133
+        (
1134
+            (?:".*?")|          # double quotes
1135
+            (?:'.*?')|          # single quotes
1136
+            (?:[^'",\#]?.*?)       # unquoted
1137
+        )
1138
+        \s*,\s*                 # comma
1139
+        ''',
1140
+        re.VERBOSE)
1141
+
1142
+    # this regexp is used for the value
1143
+    # when lists are switched off
1144
+    _nolistvalue = re.compile(r'''^
1145
+        (
1146
+            (?:".*?")|          # double quotes
1147
+            (?:'.*?')|          # single quotes
1148
+            (?:[^'"\#].*?)|     # unquoted
1149
+            (?:)                # Empty value
1150
+        )
1151
+        \s*(\#.*)?              # optional comment
1152
+        $''',
1153
+        re.VERBOSE)
1154
+
1155
+    # regexes for finding triple quoted values on one line
1156
+    _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1157
+    _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1158
+    _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1159
+    _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1160
+
1161
+    _triple_quote = {
1162
+        "'''": (_single_line_single, _multi_line_single),
1163
+        '"""': (_single_line_double, _multi_line_double),
1164
+    }
1165
+
1166
+    # Used by the ``istrue`` Section method
1167
+    _bools = {
1168
+        'yes': True, 'no': False,
1169
+        'on': True, 'off': False,
1170
+        '1': True, '0': False,
1171
+        'true': True, 'false': False,
1172
+        }
1173
+
1174
+
1175
+    def __init__(self, infile=None, options=None, configspec=None, encoding=None,
1176
+                 interpolation=True, raise_errors=False, list_values=True,
1177
+                 create_empty=False, file_error=False, stringify=True,
1178
+                 indent_type=None, default_encoding=None, unrepr=False,
1179
+                 write_empty_values=False, _inspec=False):
1180
+        """
1181
+        Parse a config file or create a config file object.
1182
+        
1183
+        ``ConfigObj(infile=None, configspec=None, encoding=None,
1184
+                    interpolation=True, raise_errors=False, list_values=True,
1185
+                    create_empty=False, file_error=False, stringify=True,
1186
+                    indent_type=None, default_encoding=None, unrepr=False,
1187
+                    write_empty_values=False, _inspec=False)``
1188
+        """
1189
+        self._inspec = _inspec
1190
+        # init the superclass
1191
+        Section.__init__(self, self, 0, self)
1192
+        
1193
+        infile = infile or []
1194
+        
1195
+        _options = {'configspec': configspec,
1196
+                    'encoding': encoding, 'interpolation': interpolation,
1197
+                    'raise_errors': raise_errors, 'list_values': list_values,
1198
+                    'create_empty': create_empty, 'file_error': file_error,
1199
+                    'stringify': stringify, 'indent_type': indent_type,
1200
+                    'default_encoding': default_encoding, 'unrepr': unrepr,
1201
+                    'write_empty_values': write_empty_values}
1202
+
1203
+        if options is None:
1204
+            options = _options
1205
+        else:
1206
+            import warnings
1207
+            warnings.warn('Passing in an options dictionary to ConfigObj() is '
1208
+                          'deprecated. Use **options instead.',
1209
+                          DeprecationWarning, stacklevel=2)
1210
+            
1211
+            # TODO: check the values too.
1212
+            for entry in options:
1213
+                if entry not in OPTION_DEFAULTS:
1214
+                    raise TypeError('Unrecognised option "%s".' % entry)
1215
+            for entry, value in list(OPTION_DEFAULTS.items()):
1216
+                if entry not in options:
1217
+                    options[entry] = value
1218
+                keyword_value = _options[entry]
1219
+                if value != keyword_value:
1220
+                    options[entry] = keyword_value
1221
+        
1222
+        # XXXX this ignores an explicit list_values = True in combination
1223
+        # with _inspec. The user should *never* do that anyway, but still...
1224
+        if _inspec:
1225
+            options['list_values'] = False
1226
+        
1227
+        self._initialise(options)
1228
+        configspec = options['configspec']
1229
+        self._original_configspec = configspec
1230
+        self._load(infile, configspec)
1231
+        
1232
+        
1233
+    def _load(self, infile, configspec):
1234
+        if isinstance(infile, six.string_types):
1235
+            self.filename = infile
1236
+            if os.path.isfile(infile):
1237
+                with open(infile, 'rb') as h:
1238
+                    content = h.readlines() or []
1239
+            elif self.file_error:
1240
+                # raise an error if the file doesn't exist
1241
+                raise IOError('Config file not found: "%s".' % self.filename)
1242
+            else:
1243
+                # file doesn't already exist
1244
+                if self.create_empty:
1245
+                    # this is a good test that the filename specified
1246
+                    # isn't impossible - like on a non-existent device
1247
+                    with open(infile, 'w') as h:
1248
+                        h.write('')
1249
+                content = []
1250
+                
1251
+        elif isinstance(infile, (list, tuple)):
1252
+            content = list(infile)
1253
+            
1254
+        elif isinstance(infile, dict):
1255
+            # initialise self
1256
+            # the Section class handles creating subsections
1257
+            if isinstance(infile, ConfigObj):
1258
+                # get a copy of our ConfigObj
1259
+                def set_section(in_section, this_section):
1260
+                    for entry in in_section.scalars:
1261
+                        this_section[entry] = in_section[entry]
1262
+                    for section in in_section.sections:
1263
+                        this_section[section] = {}
1264
+                        set_section(in_section[section], this_section[section])
1265
+                set_section(infile, self)
1266
+                
1267
+            else:
1268
+                for entry in infile:
1269
+                    self[entry] = infile[entry]
1270
+            del self._errors
1271
+            
1272
+            if configspec is not None:
1273
+                self._handle_configspec(configspec)
1274
+            else:
1275
+                self.configspec = None
1276
+            return
1277
+        
1278
+        elif getattr(infile, 'read', MISSING) is not MISSING:
1279
+            # This supports file like objects
1280
+            content = infile.read() or []
1281
+            # needs splitting into lines - but needs doing *after* decoding
1282
+            # in case it's not an 8 bit encoding
1283
+        else:
1284
+            raise TypeError('infile must be a filename, file like object, or list of lines.')
1285
+
1286
+        if content:
1287
+            # don't do it for the empty ConfigObj
1288
+            content = self._handle_bom(content)
1289
+            # infile is now *always* a list
1290
+            #
1291
+            # Set the newlines attribute (first line ending it finds)
1292
+            # and strip trailing '\n' or '\r' from lines
1293
+            for line in content:
1294
+                if (not line) or (line[-1] not in ('\r', '\n')):
1295
+                    continue
1296
+                for end in ('\r\n', '\n', '\r'):
1297
+                    if line.endswith(end):
1298
+                        self.newlines = end
1299
+                        break
1300
+                break
1301
+
1302
+        assert all(isinstance(line, six.string_types) for line in content), repr(content)
1303
+        content = [line.rstrip('\r\n') for line in content]
1304
+            
1305
+        self._parse(content)
1306
+        # if we had any errors, now is the time to raise them
1307
+        if self._errors:
1308
+            info = "at line %s." % self._errors[0].line_number
1309
+            if len(self._errors) > 1:
1310
+                msg = "Parsing failed with several errors.\nFirst error %s" % info
1311
+                error = ConfigObjError(msg)
1312
+            else:
1313
+                error = self._errors[0]
1314
+            # set the errors attribute; it's a list of tuples:
1315
+            # (error_type, message, line_number)
1316
+            error.errors = self._errors
1317
+            # set the config attribute
1318
+            error.config = self
1319
+            raise error
1320
+        # delete private attributes
1321
+        del self._errors
1322
+        
1323
+        if configspec is None:
1324
+            self.configspec = None
1325
+        else:
1326
+            self._handle_configspec(configspec)
1327
+    
1328
+    
1329
+    def _initialise(self, options=None):
1330
+        if options is None:
1331
+            options = OPTION_DEFAULTS
1332
+            
1333
+        # initialise a few variables
1334
+        self.filename = None
1335
+        self._errors = []
1336
+        self.raise_errors = options['raise_errors']
1337
+        self.interpolation = options['interpolation']
1338
+        self.list_values = options['list_values']
1339
+        self.create_empty = options['create_empty']
1340
+        self.file_error = options['file_error']
1341
+        self.stringify = options['stringify']
1342
+        self.indent_type = options['indent_type']
1343
+        self.encoding = options['encoding']
1344
+        self.default_encoding = options['default_encoding']
1345
+        self.BOM = False
1346
+        self.newlines = None
1347
+        self.write_empty_values = options['write_empty_values']
1348
+        self.unrepr = options['unrepr']
1349
+        
1350
+        self.initial_comment = []
1351
+        self.final_comment = []
1352
+        self.configspec = None
1353
+        
1354
+        if self._inspec:
1355
+            self.list_values = False
1356
+        
1357
+        # Clear section attributes as well
1358
+        Section._initialise(self)
1359
+        
1360
+        
1361
+    def __repr__(self):
1362
+        def _getval(key):
1363
+            try:
1364
+                return self[key]
1365
+            except MissingInterpolationOption:
1366
+                return dict.__getitem__(self, key)
1367
+        return ('ConfigObj({%s})' % 
1368
+                ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) 
1369
+                for key in (self.scalars + self.sections)]))
1370
+    
1371
+    
1372
+    def _handle_bom(self, infile):
1373
+        """
1374
+        Handle any BOM, and decode if necessary.
1375
+        
1376
+        If an encoding is specified, that *must* be used - but the BOM should
1377
+        still be removed (and the BOM attribute set).
1378
+        
1379
+        (If the encoding is wrongly specified, then a BOM for an alternative
1380
+        encoding won't be discovered or removed.)
1381
+        
1382
+        If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1383
+        removed. The BOM attribute will be set. UTF16 will be decoded to
1384
+        unicode.
1385
+        
1386
+        NOTE: This method must not be called with an empty ``infile``.
1387
+        
1388
+        Specifying the *wrong* encoding is likely to cause a
1389
+        ``UnicodeDecodeError``.
1390
+        
1391
+        ``infile`` must always be returned as a list of lines, but may be
1392
+        passed in as a single string.
1393
+        """
1394
+
1395
+        if ((self.encoding is not None) and
1396
+            (self.encoding.lower() not in BOM_LIST)):
1397
+            # No need to check for a BOM
1398
+            # the encoding specified doesn't have one
1399
+            # just decode
1400
+            return self._decode(infile, self.encoding)
1401
+        
1402
+        if isinstance(infile, (list, tuple)):
1403
+            line = infile[0]
1404
+        else:
1405
+            line = infile
1406
+
1407
+        if isinstance(line, six.text_type):
1408
+            # it's already decoded and there's no need to do anything
1409
+            # else, just use the _decode utility method to handle
1410
+            # listifying appropriately
1411
+            return self._decode(infile, self.encoding)
1412
+
1413
+        if self.encoding is not None:
1414
+            # encoding explicitly supplied
1415
+            # And it could have an associated BOM
1416
+            # TODO: if encoding is just UTF16 - we ought to check for both
1417
+            # TODO: big endian and little endian versions.
1418
+            enc = BOM_LIST[self.encoding.lower()]
1419
+            if enc == 'utf_16':
1420
+                # For UTF16 we try big endian and little endian
1421
+                for BOM, (encoding, final_encoding) in list(BOMS.items()):
1422
+                    if not final_encoding:
1423
+                        # skip UTF8
1424
+                        continue
1425
+                    if infile.startswith(BOM):
1426
+                        ### BOM discovered
1427
+                        ##self.BOM = True
1428
+                        # Don't need to remove BOM
1429
+                        return self._decode(infile, encoding)
1430
+                    
1431
+                # If we get this far, will *probably* raise a DecodeError
1432
+                # As it doesn't appear to start with a BOM
1433
+                return self._decode(infile, self.encoding)
1434
+            
1435
+            # Must be UTF8
1436
+            BOM = BOM_SET[enc]
1437
+            if not line.startswith(BOM):
1438
+                return self._decode(infile, self.encoding)
1439
+            
1440
+            newline = line[len(BOM):]
1441
+            
1442
+            # BOM removed
1443
+            if isinstance(infile, (list, tuple)):
1444
+                infile[0] = newline
1445
+            else:
1446
+                infile = newline
1447
+            self.BOM = True
1448
+            return self._decode(infile, self.encoding)
1449
+        
1450
+        # No encoding specified - so we need to check for UTF8/UTF16
1451
+        for BOM, (encoding, final_encoding) in list(BOMS.items()):
1452
+            if not isinstance(line, six.binary_type) or not line.startswith(BOM):
1453
+                # didn't specify a BOM, or it's not a bytestring
1454
+                continue
1455
+            else:
1456
+                # BOM discovered
1457
+                self.encoding = final_encoding
1458
+                if not final_encoding:
1459
+                    self.BOM = True
1460
+                    # UTF8
1461
+                    # remove BOM
1462
+                    newline = line[len(BOM):]
1463
+                    if isinstance(infile, (list, tuple)):
1464
+                        infile[0] = newline
1465
+                    else:
1466
+                        infile = newline
1467
+                    # UTF-8
1468
+                    if isinstance(infile, six.text_type):
1469
+                        return infile.splitlines(True)
1470
+                    elif isinstance(infile, six.binary_type):
1471
+                        return infile.decode('utf-8').splitlines(True)
1472
+                    else:
1473
+                        return self._decode(infile, 'utf-8')
1474
+                # UTF16 - have to decode
1475
+                return self._decode(infile, encoding)
1476
+            
1477
+
1478
+        if six.PY2 and isinstance(line, str):
1479
+            # don't actually do any decoding, since we're on python 2 and
1480
+            # returning a bytestring is fine
1481
+            return self._decode(infile, None)
1482
+        # No BOM discovered and no encoding specified, default to UTF-8
1483
+        if isinstance(infile, six.binary_type):
1484
+            return infile.decode('utf-8').splitlines(True)
1485
+        else:
1486
+            return self._decode(infile, 'utf-8')
1487
+
1488
+
1489
+    def _a_to_u(self, aString):
1490
+        """Decode ASCII strings to unicode if a self.encoding is specified."""
1491
+        if isinstance(aString, six.binary_type) and self.encoding:
1492
+            return aString.decode(self.encoding)
1493
+        else:
1494
+            return aString
1495
+
1496
+
1497
+    def _decode(self, infile, encoding):
1498
+        """
1499
+        Decode infile to unicode. Using the specified encoding.
1500
+        
1501
+        if is a string, it also needs converting to a list.
1502
+        """
1503
+        if isinstance(infile, six.string_types):
1504
+            return infile.splitlines(True)
1505
+        if isinstance(infile, six.binary_type):
1506
+            # NOTE: Could raise a ``UnicodeDecodeError``
1507
+            if encoding:
1508
+                return infile.decode(encoding).splitlines(True)
1509
+            else:
1510
+                return infile.splitlines(True)
1511
+
1512
+        if encoding:
1513
+            for i, line in enumerate(infile):
1514
+                if isinstance(line, six.binary_type):
1515
+                    # NOTE: The isinstance test here handles mixed lists of unicode/string
1516
+                    # NOTE: But the decode will break on any non-string values
1517
+                    # NOTE: Or could raise a ``UnicodeDecodeError``
1518
+                    infile[i] = line.decode(encoding)
1519
+        return infile
1520
+
1521
+
1522
+    def _decode_element(self, line):
1523
+        """Decode element to unicode if necessary."""
1524
+        if isinstance(line, six.binary_type) and self.default_encoding:
1525
+            return line.decode(self.default_encoding)
1526
+        else:
1527
+            return line
1528
+
1529
+
1530
+    # TODO: this may need to be modified
1531
+    def _str(self, value):
1532
+        """
1533
+        Used by ``stringify`` within validate, to turn non-string values
1534
+        into strings.
1535
+        """
1536
+        if not isinstance(value, six.string_types):
1537
+            # intentially 'str' because it's just whatever the "normal"
1538
+            # string type is for the python version we're dealing with
1539
+            return str(value)
1540
+        else:
1541
+            return value
1542
+
1543
+
1544
+    def _parse(self, infile):
1545
+        """Actually parse the config file."""
1546
+        temp_list_values = self.list_values
1547
+        if self.unrepr:
1548
+            self.list_values = False
1549
+            
1550
+        comment_list = []
1551
+        done_start = False
1552
+        this_section = self
1553
+        maxline = len(infile) - 1
1554
+        cur_index = -1
1555
+        reset_comment = False
1556
+        
1557
+        while cur_index < maxline:
1558
+            if reset_comment:
1559
+                comment_list = []
1560
+            cur_index += 1
1561
+            line = infile[cur_index]
1562
+            sline = line.strip()
1563
+            # do we have anything on the line ?
1564
+            if not sline or sline.startswith('#'):
1565
+                reset_comment = False
1566
+                comment_list.append(line)
1567
+                continue
1568
+            
1569
+            if not done_start:
1570
+                # preserve initial comment
1571
+                self.initial_comment = comment_list
1572
+                comment_list = []
1573
+                done_start = True
1574
+                
1575
+            reset_comment = True
1576
+            # first we check if it's a section marker
1577
+            mat = self._sectionmarker.match(line)
1578
+            if mat is not None:
1579
+                # is a section line
1580
+                (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
1581
+                if indent and (self.indent_type is None):
1582
+                    self.indent_type = indent
1583
+                cur_depth = sect_open.count('[')
1584
+                if cur_depth != sect_close.count(']'):
1585
+                    self._handle_error("Cannot compute the section depth",
1586
+                                       NestingError, infile, cur_index)
1587
+                    continue
1588
+                
1589
+                if cur_depth < this_section.depth:
1590
+                    # the new section is dropping back to a previous level
1591
+                    try:
1592
+                        parent = self._match_depth(this_section,
1593
+                                                   cur_depth).parent
1594
+                    except SyntaxError:
1595
+                        self._handle_error("Cannot compute nesting level",
1596
+                                           NestingError, infile, cur_index)
1597
+                        continue
1598
+                elif cur_depth == this_section.depth:
1599
+                    # the new section is a sibling of the current section
1600
+                    parent = this_section.parent
1601
+                elif cur_depth == this_section.depth + 1:
1602
+                    # the new section is a child the current section
1603
+                    parent = this_section
1604
+                else:
1605
+                    self._handle_error("Section too nested",
1606
+                                       NestingError, infile, cur_index)
1607
+                    continue
1608
+                    
1609
+                sect_name = self._unquote(sect_name)
1610
+                if sect_name in parent:
1611
+                    self._handle_error('Duplicate section name',
1612
+                                       DuplicateError, infile, cur_index)
1613
+                    continue
1614
+                
1615
+                # create the new section
1616
+                this_section = Section(
1617
+                    parent,
1618
+                    cur_depth,
1619
+                    self,
1620
+                    name=sect_name)
1621
+                parent[sect_name] = this_section
1622
+                parent.inline_comments[sect_name] = comment
1623
+                parent.comments[sect_name] = comment_list
1624
+                continue
1625
+            #
1626
+            # it's not a section marker,
1627
+            # so it should be a valid ``key = value`` line
1628
+            mat = self._keyword.match(line)
1629
+            if mat is None:
1630
+                self._handle_error(
1631
+                    'Invalid line ({0!r}) (matched as neither section nor keyword)'.format(line),
1632
+                    ParseError, infile, cur_index)
1633
+            else:
1634
+                # is a keyword value
1635
+                # value will include any inline comment
1636
+                (indent, key, value) = mat.groups()
1637
+                if indent and (self.indent_type is None):
1638
+                    self.indent_type = indent
1639
+                # check for a multiline value
1640
+                if value[:3] in ['"""', "'''"]:
1641
+                    try:
1642
+                        value, comment, cur_index = self._multiline(
1643
+                            value, infile, cur_index, maxline)
1644
+                    except SyntaxError:
1645
+                        self._handle_error(
1646
+                            'Parse error in multiline value',
1647
+                            ParseError, infile, cur_index)
1648
+                        continue
1649
+                    else:
1650
+                        if self.unrepr:
1651
+                            comment = ''
1652
+                            try:
1653
+                                value = unrepr(value)
1654
+                            except Exception as e:
1655
+                                if type(e) == UnknownType:
1656
+                                    msg = 'Unknown name or type in value'
1657
+                                else:
1658
+                                    msg = 'Parse error from unrepr-ing multiline value'
1659
+                                self._handle_error(msg, UnreprError, infile,
1660
+                                    cur_index)
1661
+                                continue
1662
+                else:
1663
+                    if self.unrepr:
1664
+                        comment = ''
1665
+                        try:
1666
+                            value = unrepr(value)
1667
+                        except Exception as e:
1668
+                            if isinstance(e, UnknownType):
1669
+                                msg = 'Unknown name or type in value'
1670
+                            else:
1671
+                                msg = 'Parse error from unrepr-ing value'
1672
+                            self._handle_error(msg, UnreprError, infile,
1673
+                                cur_index)
1674
+                            continue
1675
+                    else:
1676
+                        # extract comment and lists
1677
+                        try:
1678
+                            (value, comment) = self._handle_value(value)
1679
+                        except SyntaxError:
1680
+                            self._handle_error(
1681
+                                'Parse error in value',
1682
+                                ParseError, infile, cur_index)
1683
+                            continue
1684
+                #
1685
+                key = self._unquote(key)
1686
+                if key in this_section:
1687
+                    self._handle_error(
1688
+                        'Duplicate keyword name',
1689
+                        DuplicateError, infile, cur_index)
1690
+                    continue
1691
+                # add the key.
1692
+                # we set unrepr because if we have got this far we will never
1693
+                # be creating a new section
1694
+                this_section.__setitem__(key, value, unrepr=True)
1695
+                this_section.inline_comments[key] = comment
1696
+                this_section.comments[key] = comment_list
1697
+                continue
1698
+        #
1699
+        if self.indent_type is None:
1700
+            # no indentation used, set the type accordingly
1701
+            self.indent_type = ''
1702
+
1703
+        # preserve the final comment
1704
+        if not self and not self.initial_comment:
1705
+            self.initial_comment = comment_list
1706
+        elif not reset_comment:
1707
+            self.final_comment = comment_list
1708
+        self.list_values = temp_list_values
1709
+
1710
+
1711
+    def _match_depth(self, sect, depth):
1712
+        """
1713
+        Given a section and a depth level, walk back through the sections
1714
+        parents to see if the depth level matches a previous section.
1715
+        
1716
+        Return a reference to the right section,
1717
+        or raise a SyntaxError.
1718
+        """
1719
+        while depth < sect.depth:
1720
+            if sect is sect.parent:
1721
+                # we've reached the top level already
1722
+                raise SyntaxError()
1723
+            sect = sect.parent
1724
+        if sect.depth == depth:
1725
+            return sect
1726
+        # shouldn't get here
1727
+        raise SyntaxError()
1728
+
1729
+
1730
+    def _handle_error(self, text, ErrorClass, infile, cur_index):
1731
+        """
1732
+        Handle an error according to the error settings.
1733
+        
1734
+        Either raise the error or store it.
1735
+        The error will have occured at ``cur_index``
1736
+        """
1737
+        line = infile[cur_index]
1738
+        cur_index += 1
1739
+        message = '{0} at line {1}.'.format(text, cur_index)
1740
+        error = ErrorClass(message, cur_index, line)
1741
+        if self.raise_errors:
1742
+            # raise the error - parsing stops here
1743
+            raise error
1744
+        # store the error
1745
+        # reraise when parsing has finished
1746
+        self._errors.append(error)
1747
+
1748
+
1749
+    def _unquote(self, value):
1750
+        """Return an unquoted version of a value"""
1751
+        if not value:
1752
+            # should only happen during parsing of lists
1753
+            raise SyntaxError
1754
+        if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1755
+            value = value[1:-1]
1756
+        return value
1757
+
1758
+
1759
+    def _quote(self, value, multiline=True):
1760
+        """
1761
+        Return a safely quoted version of a value.
1762
+        
1763
+        Raise a ConfigObjError if the value cannot be safely quoted.
1764
+        If multiline is ``True`` (default) then use triple quotes
1765
+        if necessary.
1766
+        
1767
+        * Don't quote values that don't need it.
1768
+        * Recursively quote members of a list and return a comma joined list.
1769
+        * Multiline is ``False`` for lists.
1770
+        * Obey list syntax for empty and single member lists.
1771
+        
1772
+        If ``list_values=False`` then the value is only quoted if it contains
1773
+        a ``\\n`` (is multiline) or '#'.
1774
+        
1775
+        If ``write_empty_values`` is set, and the value is an empty string, it
1776
+        won't be quoted.
1777
+        """
1778
+        if multiline and self.write_empty_values and value == '':
1779
+            # Only if multiline is set, so that it is used for values not
1780
+            # keys, and not values that are part of a list
1781
+            return ''
1782
+        
1783
+        if multiline and isinstance(value, (list, tuple)):
1784
+            if not value:
1785
+                return ','
1786
+            elif len(value) == 1:
1787
+                return self._quote(value[0], multiline=False) + ','
1788
+            return ', '.join([self._quote(val, multiline=False)
1789
+                for val in value])
1790
+        if not isinstance(value, six.string_types):
1791
+            if self.stringify:
1792
+                # intentially 'str' because it's just whatever the "normal"
1793
+                # string type is for the python version we're dealing with
1794
+                value = str(value)
1795
+            else:
1796
+                raise TypeError('Value "%s" is not a string.' % value)
1797
+
1798
+        if not value:
1799
+            return '""'
1800
+        
1801
+        no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1802
+        need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1803
+        hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1804
+        check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1805
+        
1806
+        if check_for_single:
1807
+            if not self.list_values:
1808
+                # we don't quote if ``list_values=False``
1809
+                quot = noquot
1810
+            # for normal values either single or double quotes will do
1811
+            elif '\n' in value:
1812
+                # will only happen if multiline is off - e.g. '\n' in key
1813
+                raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1814
+            elif ((value[0] not in wspace_plus) and
1815
+                    (value[-1] not in wspace_plus) and
1816
+                    (',' not in value)):
1817
+                quot = noquot
1818
+            else:
1819
+                quot = self._get_single_quote(value)
1820
+        else:
1821
+            # if value has '\n' or "'" *and* '"', it will need triple quotes
1822
+            quot = self._get_triple_quote(value)
1823
+        
1824
+        if quot == noquot and '#' in value and self.list_values:
1825
+            quot = self._get_single_quote(value)
1826
+                
1827
+        return quot % value
1828
+    
1829
+    
1830
+    def _get_single_quote(self, value):
1831
+        if ("'" in value) and ('"' in value):
1832
+            raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1833
+        elif '"' in value:
1834
+            quot = squot
1835
+        else:
1836
+            quot = dquot
1837
+        return quot
1838
+    
1839
+    
1840
+    def _get_triple_quote(self, value):
1841
+        if (value.find('"""') != -1) and (value.find("'''") != -1):
1842
+            raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1843
+        if value.find('"""') == -1:
1844
+            quot = tdquot
1845
+        else:
1846
+            quot = tsquot 
1847
+        return quot
1848
+
1849
+
1850
+    def _handle_value(self, value):
1851
+        """
1852
+        Given a value string, unquote, remove comment,
1853
+        handle lists. (including empty and single member lists)
1854
+        """
1855
+        if self._inspec:
1856
+            # Parsing a configspec so don't handle comments
1857
+            return (value, '')
1858
+        # do we look for lists in values ?
1859
+        if not self.list_values:
1860
+            mat = self._nolistvalue.match(value)
1861
+            if mat is None:
1862
+                raise SyntaxError()
1863
+            # NOTE: we don't unquote here
1864
+            return mat.groups()
1865
+        #
1866
+        mat = self._valueexp.match(value)
1867
+        if mat is None:
1868
+            # the value is badly constructed, probably badly quoted,
1869
+            # or an invalid list
1870
+            raise SyntaxError()
1871
+        (list_values, single, empty_list, comment) = mat.groups()
1872
+        if (list_values == '') and (single is None):
1873
+            # change this if you want to accept empty values
1874
+            raise SyntaxError()
1875
+        # NOTE: note there is no error handling from here if the regex
1876
+        # is wrong: then incorrect values will slip through
1877
+        if empty_list is not None:
1878
+            # the single comma - meaning an empty list
1879
+            return ([], comment)
1880
+        if single is not None:
1881
+            # handle empty values
1882
+            if list_values and not single:
1883
+                # FIXME: the '' is a workaround because our regex now matches
1884
+                #   '' at the end of a list if it has a trailing comma
1885
+                single = None
1886
+            else:
1887
+                single = single or '""'
1888
+                single = self._unquote(single)
1889
+        if list_values == '':
1890
+            # not a list value
1891
+            return (single, comment)
1892
+        the_list = self._listvalueexp.findall(list_values)
1893
+        the_list = [self._unquote(val) for val in the_list]
1894
+        if single is not None:
1895
+            the_list += [single]
1896
+        return (the_list, comment)
1897
+
1898
+
1899
+    def _multiline(self, value, infile, cur_index, maxline):
1900
+        """Extract the value, where we are in a multiline situation."""
1901
+        quot = value[:3]
1902
+        newvalue = value[3:]
1903
+        single_line = self._triple_quote[quot][0]
1904
+        multi_line = self._triple_quote[quot][1]
1905
+        mat = single_line.match(value)
1906
+        if mat is not None:
1907
+            retval = list(mat.groups())
1908
+            retval.append(cur_index)
1909
+            return retval
1910
+        elif newvalue.find(quot) != -1:
1911
+            # somehow the triple quote is missing
1912
+            raise SyntaxError()
1913
+        #
1914
+        while cur_index < maxline:
1915
+            cur_index += 1
1916
+            newvalue += '\n'
1917
+            line = infile[cur_index]
1918
+            if line.find(quot) == -1:
1919
+                newvalue += line
1920
+            else:
1921
+                # end of multiline, process it
1922
+                break
1923
+        else:
1924
+            # we've got to the end of the config, oops...
1925
+            raise SyntaxError()
1926
+        mat = multi_line.match(line)
1927
+        if mat is None:
1928
+            # a badly formed line
1929
+            raise SyntaxError()
1930
+        (value, comment) = mat.groups()
1931
+        return (newvalue + value, comment, cur_index)
1932
+
1933
+
1934
+    def _handle_configspec(self, configspec):
1935
+        """Parse the configspec."""
1936
+        # FIXME: Should we check that the configspec was created with the 
1937
+        #        correct settings ? (i.e. ``list_values=False``)
1938
+        if not isinstance(configspec, ConfigObj):
1939
+            try:
1940
+                configspec = ConfigObj(configspec,
1941
+                                       raise_errors=True,
1942
+                                       file_error=True,
1943
+                                       _inspec=True)
1944
+            except ConfigObjError as e:
1945
+                # FIXME: Should these errors have a reference
1946
+                #        to the already parsed ConfigObj ?
1947
+                raise ConfigspecError('Parsing configspec failed: %s' % e)
1948
+            except IOError as e:
1949
+                raise IOError('Reading configspec failed: %s' % e)
1950
+        
1951
+        self.configspec = configspec
1952
+            
1953
+
1954
+        
1955
+    def _set_configspec(self, section, copy):
1956
+        """
1957
+        Called by validate. Handles setting the configspec on subsections
1958
+        including sections to be validated by __many__
1959
+        """
1960
+        configspec = section.configspec
1961
+        many = configspec.get('__many__')
1962
+        if isinstance(many, dict):
1963
+            for entry in section.sections:
1964
+                if entry not in configspec:
1965
+                    section[entry].configspec = many
1966
+                    
1967
+        for entry in configspec.sections:
1968
+            if entry == '__many__':
1969
+                continue
1970
+            if entry not in section:
1971
+                section[entry] = {}
1972
+                section[entry]._created = True
1973
+                if copy:
1974
+                    # copy comments
1975
+                    section.comments[entry] = configspec.comments.get(entry, [])
1976
+                    section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
1977
+                
1978
+            # Could be a scalar when we expect a section
1979
+            if isinstance(section[entry], Section):
1980
+                section[entry].configspec = configspec[entry]
1981
+                        
1982
+
1983
+    def _write_line(self, indent_string, entry, this_entry, comment):
1984
+        """Write an individual line, for the write method"""
1985
+        # NOTE: the calls to self._quote here handles non-StringType values.
1986
+        if not self.unrepr:
1987
+            val = self._decode_element(self._quote(this_entry))
1988
+        else:
1989
+            val = repr(this_entry)
1990
+        return '%s%s%s%s%s' % (indent_string,
1991
+                               self._decode_element(self._quote(entry, multiline=False)),
1992
+                               self._a_to_u(' = '),
1993
+                               val,
1994
+                               self._decode_element(comment))
1995
+
1996
+
1997
+    def _write_marker(self, indent_string, depth, entry, comment):
1998
+        """Write a section marker line"""
1999
+        return '%s%s%s%s%s' % (indent_string,
2000
+                               self._a_to_u('[' * depth),
2001
+                               self._quote(self._decode_element(entry), multiline=False),
2002
+                               self._a_to_u(']' * depth),
2003
+                               self._decode_element(comment))
2004
+
2005
+
2006
+    def _handle_comment(self, comment):
2007
+        """Deal with a comment."""
2008
+        if not comment:
2009
+            return ''
2010
+        start = self.indent_type
2011
+        if not comment.startswith('#'):
2012
+            start += self._a_to_u(' # ')
2013
+        return (start + comment)
2014
+
2015
+
2016
+    # Public methods
2017
+
2018
+    def write(self, outfile=None, section=None):
2019
+        """
2020
+        Write the current ConfigObj as a file
2021
+        
2022
+        tekNico: FIXME: use StringIO instead of real files
2023
+        
2024
+        >>> filename = a.filename
2025
+        >>> a.filename = 'test.ini'
2026
+        >>> a.write()
2027
+        >>> a.filename = filename
2028
+        >>> a == ConfigObj('test.ini', raise_errors=True)
2029
+        1
2030
+        >>> import os
2031
+        >>> os.remove('test.ini')
2032
+        """
2033
+        if self.indent_type is None:
2034
+            # this can be true if initialised from a dictionary
2035
+            self.indent_type = DEFAULT_INDENT_TYPE
2036
+            
2037
+        out = []
2038
+        cs = self._a_to_u('#')
2039
+        csp = self._a_to_u('# ')
2040
+        if section is None:
2041
+            int_val = self.interpolation
2042
+            self.interpolation = False
2043
+            section = self
2044
+            for line in self.initial_comment:
2045
+                line = self._decode_element(line)
2046
+                stripped_line = line.strip()
2047
+                if stripped_line and not stripped_line.startswith(cs):
2048
+                    line = csp + line
2049
+                out.append(line)
2050
+                
2051
+        indent_string = self.indent_type * section.depth
2052
+        for entry in (section.scalars + section.sections):
2053
+            if entry in section.defaults:
2054
+                # don't write out default values
2055
+                continue
2056
+            for comment_line in section.comments[entry]:
2057
+                comment_line = self._decode_element(comment_line.lstrip())
2058
+                if comment_line and not comment_line.startswith(cs):
2059
+                    comment_line = csp + comment_line
2060
+                out.append(indent_string + comment_line)
2061
+            this_entry = section[entry]
2062
+            comment = self._handle_comment(section.inline_comments[entry])
2063
+            
2064
+            if isinstance(this_entry, Section):
2065
+                # a section
2066
+                out.append(self._write_marker(
2067
+                    indent_string,
2068
+                    this_entry.depth,
2069
+                    entry,
2070
+                    comment))
2071
+                out.extend(self.write(section=this_entry))
2072
+            else:
2073
+                out.append(self._write_line(
2074
+                    indent_string,
2075
+                    entry,
2076
+                    this_entry,
2077
+                    comment))
2078
+                
2079
+        if section is self:
2080
+            for line in self.final_comment:
2081
+                line = self._decode_element(line)
2082
+                stripped_line = line.strip()
2083
+                if stripped_line and not stripped_line.startswith(cs):
2084
+                    line = csp + line
2085
+                out.append(line)
2086
+            self.interpolation = int_val
2087
+            
2088
+        if section is not self:
2089
+            return out
2090
+        
2091
+        if (self.filename is None) and (outfile is None):
2092
+            # output a list of lines
2093
+            # might need to encode
2094
+            # NOTE: This will *screw* UTF16, each line will start with the BOM
2095
+            if self.encoding:
2096
+                out = [l.encode(self.encoding) for l in out]
2097
+            if (self.BOM and ((self.encoding is None) or
2098
+                (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2099
+                # Add the UTF8 BOM
2100
+                if not out:
2101
+                    out.append('')
2102
+                out[0] = BOM_UTF8 + out[0]
2103
+            return out
2104
+        
2105
+        # Turn the list to a string, joined with correct newlines
2106
+        newline = self.newlines or os.linesep
2107
+        if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w'
2108
+            and sys.platform == 'win32' and newline == '\r\n'):
2109
+            # Windows specific hack to avoid writing '\r\r\n'
2110
+            newline = '\n'
2111
+        output = self._a_to_u(newline).join(out)
2112
+        if not output.endswith(newline):
2113
+            output += newline
2114
+
2115
+        if isinstance(output, six.binary_type):
2116
+            output_bytes = output
2117
+        else:
2118
+            output_bytes = output.encode(self.encoding or
2119
+                                         self.default_encoding or
2120
+                                         'ascii')
2121
+
2122
+        if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
2123
+            # Add the UTF8 BOM
2124
+            output_bytes = BOM_UTF8 + output_bytes
2125
+
2126
+        if outfile is not None:
2127
+            outfile.write(output_bytes)
2128
+        else:
2129
+            with open(self.filename, 'wb') as h:
2130
+                h.write(output_bytes)
2131
+
2132
+    def validate(self, validator, preserve_errors=False, copy=False,
2133
+                 section=None):
2134
+        """
2135
+        Test the ConfigObj against a configspec.
2136
+        
2137
+        It uses the ``validator`` object from *validate.py*.
2138
+        
2139
+        To run ``validate`` on the current ConfigObj, call: ::
2140
+        
2141
+            test = config.validate(validator)
2142
+        
2143
+        (Normally having previously passed in the configspec when the ConfigObj
2144
+        was created - you can dynamically assign a dictionary of checks to the
2145
+        ``configspec`` attribute of a section though).
2146
+        
2147
+        It returns ``True`` if everything passes, or a dictionary of
2148
+        pass/fails (True/False). If every member of a subsection passes, it
2149
+        will just have the value ``True``. (It also returns ``False`` if all
2150
+        members fail).
2151
+        
2152
+        In addition, it converts the values from strings to their native
2153
+        types if their checks pass (and ``stringify`` is set).
2154
+        
2155
+        If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2156
+        of a marking a fail with a ``False``, it will preserve the actual
2157
+        exception object. This can contain info about the reason for failure.
2158
+        For example the ``VdtValueTooSmallError`` indicates that the value
2159
+        supplied was too small. If a value (or section) is missing it will
2160
+        still be marked as ``False``.
2161
+        
2162
+        You must have the validate module to use ``preserve_errors=True``.
2163
+        
2164
+        You can then use the ``flatten_errors`` function to turn your nested
2165
+        results dictionary into a flattened list of failures - useful for
2166
+        displaying meaningful error messages.
2167
+        """
2168
+        if section is None:
2169
+            if self.configspec is None:
2170
+                raise ValueError('No configspec supplied.')
2171
+            if preserve_errors:
2172
+                # We do this once to remove a top level dependency on the validate module
2173
+                # Which makes importing configobj faster
2174
+                from validate import VdtMissingValue
2175
+                self._vdtMissingValue = VdtMissingValue
2176
+                
2177
+            section = self
2178
+
2179
+            if copy:
2180
+                section.initial_comment = section.configspec.initial_comment
2181
+                section.final_comment = section.configspec.final_comment
2182
+                section.encoding = section.configspec.encoding
2183
+                section.BOM = section.configspec.BOM
2184
+                section.newlines = section.configspec.newlines
2185
+                section.indent_type = section.configspec.indent_type
2186
+            
2187
+        #
2188
+        # section.default_values.clear() #??
2189
+        configspec = section.configspec
2190
+        self._set_configspec(section, copy)
2191
+
2192
+        
2193
+        def validate_entry(entry, spec, val, missing, ret_true, ret_false):
2194
+            section.default_values.pop(entry, None)
2195
+                
2196
+            try:
2197
+                section.default_values[entry] = validator.get_default_value(configspec[entry])
2198
+            except (KeyError, AttributeError, validator.baseErrorClass):
2199
+                # No default, bad default or validator has no 'get_default_value'
2200
+                # (e.g. SimpleVal)
2201
+                pass
2202
+            
2203
+            try:
2204
+                check = validator.check(spec,
2205
+                                        val,
2206
+                                        missing=missing
2207
+                                        )
2208
+            except validator.baseErrorClass as e:
2209
+                if not preserve_errors or isinstance(e, self._vdtMissingValue):
2210
+                    out[entry] = False
2211
+                else:
2212
+                    # preserve the error
2213
+                    out[entry] = e
2214
+                    ret_false = False
2215
+                ret_true = False
2216
+            else:
2217
+                ret_false = False
2218
+                out[entry] = True
2219
+                if self.stringify or missing:
2220
+                    # if we are doing type conversion
2221
+                    # or the value is a supplied default
2222
+                    if not self.stringify:
2223
+                        if isinstance(check, (list, tuple)):
2224
+                            # preserve lists
2225
+                            check = [self._str(item) for item in check]
2226
+                        elif missing and check is None:
2227
+                            # convert the None from a default to a ''
2228
+                            check = ''
2229
+                        else:
2230
+                            check = self._str(check)
2231
+                    if (check != val) or missing:
2232
+                        section[entry] = check
2233
+                if not copy and missing and entry not in section.defaults:
2234
+                    section.defaults.append(entry)
2235
+            return ret_true, ret_false
2236
+        
2237
+        #
2238
+        out = {}
2239
+        ret_true = True
2240
+        ret_false = True
2241
+        
2242
+        unvalidated = [k for k in section.scalars if k not in configspec]
2243
+        incorrect_sections = [k for k in configspec.sections if k in section.scalars]        
2244
+        incorrect_scalars = [k for k in configspec.scalars if k in section.sections]
2245
+        
2246
+        for entry in configspec.scalars:
2247
+            if entry in ('__many__', '___many___'):
2248
+                # reserved names
2249
+                continue
2250
+            if (not entry in section.scalars) or (entry in section.defaults):
2251
+                # missing entries
2252
+                # or entries from defaults
2253
+                missing = True
2254
+                val = None
2255
+                if copy and entry not in section.scalars:
2256
+                    # copy comments
2257
+                    section.comments[entry] = (
2258
+                        configspec.comments.get(entry, []))
2259
+                    section.inline_comments[entry] = (
2260
+                        configspec.inline_comments.get(entry, ''))
2261
+                #
2262
+            else:
2263
+                missing = False
2264
+                val = section[entry]
2265
+            
2266
+            ret_true, ret_false = validate_entry(entry, configspec[entry], val, 
2267
+                                                 missing, ret_true, ret_false)
2268
+        
2269
+        many = None
2270
+        if '__many__' in configspec.scalars:
2271
+            many = configspec['__many__']
2272
+        elif '___many___' in configspec.scalars:
2273
+            many = configspec['___many___']
2274
+        
2275
+        if many is not None:
2276
+            for entry in unvalidated:
2277
+                val = section[entry]
2278
+                ret_true, ret_false = validate_entry(entry, many, val, False,
2279
+                                                     ret_true, ret_false)
2280
+            unvalidated = []
2281
+
2282
+        for entry in incorrect_scalars:
2283
+            ret_true = False
2284
+            if not preserve_errors:
2285
+                out[entry] = False
2286
+            else:
2287
+                ret_false = False
2288
+                msg = 'Value %r was provided as a section' % entry
2289
+                out[entry] = validator.baseErrorClass(msg)
2290
+        for entry in incorrect_sections:
2291
+            ret_true = False
2292
+            if not preserve_errors:
2293
+                out[entry] = False
2294
+            else:
2295
+                ret_false = False
2296
+                msg = 'Section %r was provided as a single value' % entry
2297
+                out[entry] = validator.baseErrorClass(msg)
2298
+                
2299
+        # Missing sections will have been created as empty ones when the
2300
+        # configspec was read.
2301
+        for entry in section.sections:
2302
+            # FIXME: this means DEFAULT is not copied in copy mode
2303
+            if section is self and entry == 'DEFAULT':
2304
+                continue
2305
+            if section[entry].configspec is None:
2306
+                unvalidated.append(entry)
2307
+                continue
2308
+            if copy:
2309
+                section.comments[entry] = configspec.comments.get(entry, [])
2310
+                section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
2311
+            check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry])
2312
+            out[entry] = check
2313
+            if check == False:
2314
+                ret_true = False
2315
+            elif check == True:
2316
+                ret_false = False
2317
+            else:
2318
+                ret_true = False
2319
+        
2320
+        section.extra_values = unvalidated
2321
+        if preserve_errors and not section._created:
2322
+            # If the section wasn't created (i.e. it wasn't missing)
2323
+            # then we can't return False, we need to preserve errors
2324
+            ret_false = False
2325
+        #
2326
+        if ret_false and preserve_errors and out:
2327
+            # If we are preserving errors, but all
2328
+            # the failures are from missing sections / values
2329
+            # then we can return False. Otherwise there is a
2330
+            # real failure that we need to preserve.
2331
+            ret_false = not any(out.values())
2332
+        if ret_true:
2333
+            return True
2334
+        elif ret_false:
2335
+            return False
2336
+        return out
2337
+
2338
+
2339
+    def reset(self):
2340
+        """Clear ConfigObj instance and restore to 'freshly created' state."""
2341
+        self.clear()
2342
+        self._initialise()
2343
+        # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
2344
+        #        requires an empty dictionary
2345
+        self.configspec = None
2346
+        # Just to be sure ;-)
2347
+        self._original_configspec = None
2348
+        
2349
+        
2350
+    def reload(self):
2351
+        """
2352
+        Reload a ConfigObj from file.
2353
+        
2354
+        This method raises a ``ReloadError`` if the ConfigObj doesn't have
2355
+        a filename attribute pointing to a file.
2356
+        """
2357
+        if not isinstance(self.filename, six.string_types):
2358
+            raise ReloadError()
2359
+
2360
+        filename = self.filename
2361
+        current_options = {}
2362
+        for entry in OPTION_DEFAULTS:
2363
+            if entry == 'configspec':
2364
+                continue
2365
+            current_options[entry] = getattr(self, entry)
2366
+            
2367
+        configspec = self._original_configspec
2368
+        current_options['configspec'] = configspec
2369
+            
2370
+        self.clear()
2371
+        self._initialise(current_options)
2372
+        self._load(filename, configspec)
2373
+        
2374
+
2375
+
2376
+class SimpleVal(object):
2377
+    """
2378
+    A simple validator.
2379
+    Can be used to check that all members expected are present.
2380
+    
2381
+    To use it, provide a configspec with all your members in (the value given
2382
+    will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2383
+    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2384
+    members are present, or a dictionary with True/False meaning
2385
+    present/missing. (Whole missing sections will be replaced with ``False``)
2386
+    """
2387
+    
2388
+    def __init__(self):
2389
+        self.baseErrorClass = ConfigObjError
2390
+    
2391
+    def check(self, check, member, missing=False):
2392
+        """A dummy check method, always returns the value unchanged."""
2393
+        if missing:
2394
+            raise self.baseErrorClass()
2395
+        return member
2396
+
2397
+
2398
+def flatten_errors(cfg, res, levels=None, results=None):
2399
+    """
2400
+    An example function that will turn a nested dictionary of results
2401
+    (as returned by ``ConfigObj.validate``) into a flat list.
2402
+    
2403
+    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2404
+    dictionary returned by ``validate``.
2405
+    
2406
+    (This is a recursive function, so you shouldn't use the ``levels`` or
2407
+    ``results`` arguments - they are used by the function.)
2408
+    
2409
+    Returns a list of keys that failed. Each member of the list is a tuple::
2410
+    
2411
+        ([list of sections...], key, result)
2412
+    
2413
+    If ``validate`` was called with ``preserve_errors=False`` (the default)
2414
+    then ``result`` will always be ``False``.
2415
+
2416
+    *list of sections* is a flattened list of sections that the key was found
2417
+    in.
2418
+    
2419
+    If the section was missing (or a section was expected and a scalar provided
2420
+    - or vice-versa) then key will be ``None``.
2421
+    
2422
+    If the value (or section) was missing then ``result`` will be ``False``.
2423
+    
2424
+    If ``validate`` was called with ``preserve_errors=True`` and a value
2425
+    was present, but failed the check, then ``result`` will be the exception
2426
+    object returned. You can use this as a string that describes the failure.
2427
+    
2428
+    For example *The value "3" is of the wrong type*.
2429
+    """
2430
+    if levels is None:
2431
+        # first time called
2432
+        levels = []
2433
+        results = []
2434
+    if res == True:
2435
+        return sorted(results)
2436
+    if res == False or isinstance(res, Exception):
2437
+        results.append((levels[:], None, res))
2438
+        if levels:
2439
+            levels.pop()
2440
+        return sorted(results)
2441
+    for (key, val) in list(res.items()):
2442
+        if val == True:
2443
+            continue
2444
+        if isinstance(cfg.get(key), dict):
2445
+            # Go down one level
2446
+            levels.append(key)
2447
+            flatten_errors(cfg[key], val, levels, results)
2448
+            continue
2449
+        results.append((levels[:], key, val))
2450
+    #
2451
+    # Go up one level
2452
+    if levels:
2453
+        levels.pop()
2454
+    #
2455
+    return sorted(results)
2456
+
2457
+
2458
+def get_extra_values(conf, _prepend=()):
2459
+    """
2460
+    Find all the values and sections not in the configspec from a validated
2461
+    ConfigObj.
2462
+    
2463
+    ``get_extra_values`` returns a list of tuples where each tuple represents
2464
+    either an extra section, or an extra value.
2465
+    
2466
+    The tuples contain two values, a tuple representing the section the value 
2467
+    is in and the name of the extra values. For extra values in the top level
2468
+    section the first member will be an empty tuple. For values in the 'foo'
2469
+    section the first member will be ``('foo',)``. For members in the 'bar'
2470
+    subsection of the 'foo' section the first member will be ``('foo', 'bar')``.
2471
+    
2472
+    NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't
2473
+    been validated it will return an empty list.
2474
+    """
2475
+    out = []
2476
+    
2477
+    out.extend([(_prepend, name) for name in conf.extra_values])
2478
+    for name in conf.sections:
2479
+        if name not in conf.extra_values:
2480
+            out.extend(get_extra_values(conf[name], _prepend + (name,)))
2481
+    return out
2482
+
2483
+
2484
+"""*A programming language is a medium of expression.* - Paul Graham"""

+ 24
- 0
lib/observer.py View File

@@ -0,0 +1,24 @@
1
+class Subscriber:
2
+    def __init__(self, name):
3
+        self.name = name
4
+    def update(self, message):
5
+        #print('{} got message "{}"'.format(self.name, message))
6
+        pass
7
+        
8
+class Publisher:
9
+    def __init__(self, events):
10
+        self.subscribers = { event : dict()
11
+                             for event in events }
12
+                             
13
+    def get_subscribers(self, event):
14
+        return self.subscribers[event]                         
15
+    def register(self, event, who, callback=None):
16
+        if callback is None:
17
+            callback = getattr(who, 'ReceiveMessage')
18
+        self.get_subscribers(event)[who] = callback
19
+    def unregister(self, event, who):
20
+        del self.get_subscribers(event)[who]
21
+    def dispatch(self, event, message):
22
+        #print('dispatch', self.get_subscribers(event).items())
23
+        for subscriber, callback in self.get_subscribers(event).items():
24
+            callback(message)     

+ 473
- 0
main.py View File

@@ -0,0 +1,473 @@
1
+
2
+import sys
3
+sys.path.insert(0, './lib')
4
+from pygame import mixer
5
+import time
6
+import digitalio
7
+import board
8
+import adafruit_matrixkeypad
9
+import pygame
10
+from PIL import Image, ImageDraw, ImageFont
11
+import adafruit_rgb_display.st7789 as st7789
12
+import digitalio
13
+import board
14
+from adafruit_rgb_display.rgb import color565
15
+import adafruit_rgb_display.st7789 as st7789
16
+import FSM
17
+from itertools import cycle
18
+import observer
19
+from scikits.samplerate import resample
20
+from configobj import ConfigObj
21
+import ast
22
+#from configobj import ConfigObj
23
+import RPi.GPIO as GPIO
24
+from time import sleep
25
+GPIO.setmode(GPIO.BCM)
26
+
27
+# set up GPIO output channel
28
+GPIO.setup(47, GPIO.OUT)
29
+
30
+
31
+cols = [digitalio.DigitalInOut(x) for x in (board.D21, board.D20, board.D16, board.D12)]
32
+rows = [digitalio.DigitalInOut(x) for x in (board.D26, board.D13, board.D6, board.D5)]
33
+# keys = ((3, 2, 1, 0),
34
+#         (7, 6, 5, 4),
35
+#         (11, 10, 9, 8),
36
+#         (15, 14, 13, 12))
37
+
38
+keys = ((15, 14, 13, 12),
39
+		(11, 10, 9, 8),
40
+		(7, 6, 5, 4),
41
+		(3, 2, 1, 0))
42
+
43
+
44
+keypad = adafruit_matrixkeypad.Matrix_Keypad(rows, cols, keys)
45
+
46
+# while True:
47
+#     keys = keypad.pressed_keys
48
+#     if keys:
49
+#         print("Pressed: ", keys)
50
+#     time.sleep(0.1)
51
+
52
+
53
+
54
+
55
+pygame.init()
56
+mixer.init()
57
+done = False
58
+clock = pygame.time.Clock()
59
+TIMER = pygame.USEREVENT + 1
60
+#pygame.time.set_timer(pygame.USEREVENT + 1, 444)
61
+#pygame.time.set_timer(TIMER, 161)
62
+playhead = 0
63
+
64
+timer = pygame.time.get_ticks
65
+start = now = timer()
66
+
67
+cs_pin = digitalio.DigitalInOut(board.CE0)
68
+dc_pin = digitalio.DigitalInOut(board.D25)
69
+reset_pin = None
70
+BAUDRATE = 64000000  # The pi can be very fast!
71
+# display = st7789.ST7789(
72
+# 	board.SPI(),
73
+# 	cs=cs_pin,
74
+# 	dc=dc_pin,
75
+# 	rst=reset_pin,
76
+# 	baudrate=BAUDRATE,
77
+# 	width=135,
78
+# 	height=240,
79
+# 	x_offset=53,
80
+# 	y_offset=40,
81
+# )
82
+
83
+backlight = digitalio.DigitalInOut(board.D22)
84
+backlight.switch_to_output()
85
+backlight.value = True
86
+buttonA = digitalio.DigitalInOut(board.D23)
87
+buttonB = digitalio.DigitalInOut(board.D24)
88
+buttonA.switch_to_input()
89
+buttonB.switch_to_input()
90
+
91
+spi = board.SPI()
92
+
93
+
94
+
95
+# Create blank image for drawing.
96
+# Make sure to create image with mode 'RGB' for full color.
97
+
98
+# Get drawing object to draw on image.
99
+
100
+
101
+# Draw a black filled box to clear the image.
102
+#draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
103
+#disp.image(image, rotation)
104
+# Draw some shapes.
105
+# First define some constants to allow easy resizing of shapes.
106
+
107
+# Move left to right keeping track of the current x position for drawing shapes.
108
+x = 0
109
+
110
+
111
+
112
+# Turn on the backlight
113
+backlight = digitalio.DigitalInOut(board.D22)
114
+backlight.switch_to_output()
115
+backlight.value = True
116
+
117
+
118
+class Prog:
119
+	def __init__(self):
120
+		self.FSM = FSM.ProgFSM(self)
121
+		self.font = ImageFont.truetype("/home/pi/examples/HLM.ttf", 64)
122
+		#self.h1 = ImageFont.truetype("/home/pi/examples/HLM.ttf", 26)
123
+		self.h1 = ImageFont.truetype("/home/pi/Pixellari.ttf", 30)
124
+		self.h2 = ImageFont.truetype("/home/pi/Pixellari.ttf", 20)
125
+		self.h3 = ImageFont.truetype("/home/pi/Pixellari.ttf", 54)
126
+		
127
+		self.disp = st7789.ST7789(
128
+			spi,
129
+			cs=cs_pin,
130
+			dc=dc_pin,
131
+			rst=reset_pin,
132
+			baudrate=BAUDRATE,
133
+			width=135,
134
+			height=240,
135
+			x_offset=53,
136
+			y_offset=40,
137
+		)
138
+
139
+		self.height = self.disp.width  # we swap height/width to rotate it to landscape!
140
+		self.width = self.disp.height
141
+
142
+		self.rotation = 270
143
+		self.padding = -2
144
+		self.top = self.padding
145
+		self.bottom = self.height - self.padding
146
+
147
+		self.image = Image.new("RGB", (self.width, self.height))
148
+		self.draw = ImageDraw.Draw(self.image)
149
+		self.playhead = 0
150
+		self.playing = False
151
+		#self.bpm = 90
152
+		self.bpm_inc = 1
153
+		self.note_bank = 0
154
+		self.volume = 10
155
+		self.note_vol = 16
156
+		self.note_pitch = 0.0
157
+		self.soundSlots = []
158
+		self.keys = []
159
+		self.keyState = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
160
+		#self.song = [2, 3, 0, 1, 0, 1, 0, 1, 0, 1]
161
+		self.song = [0]
162
+		self.songCycle = cycle(self.song)
163
+		self.curPattern = next(self.songCycle)
164
+		self.songStart = self.curPattern
165
+		self.eSound = 0
166
+		self.ePattern = 0
167
+		self.black = "#000000"
168
+		self.bg_color = "#336699"
169
+		self.color_a = "#FFFFFF"
170
+		self.color_b = "#929230"
171
+		self.color_c = "#FFFF00"
172
+		self.dark_grey = "#404040"
173
+		self.grey = "#808080"
174
+		self.light_grey = "#D3D3D3"
175
+		self.blue = "#336699"
176
+		self.dark_blue = "#2A2AD5"
177
+		self.pink = "#D52A80"
178
+		self.red = "#D52A2A"
179
+		self.olive = "#80D52A"
180
+		self.green = "#2AD52A"
181
+
182
+		self.pub = observer.Publisher(['beat', 'the joint'])
183
+
184
+		self.song_file_name = "default.sng"
185
+		self.mconf = ConfigObj("config.txt")
186
+		self.sconf = ConfigObj("/home/pi/zpc_ct/user/songs/" + self.mconf['default_song'])
187
+		self.theme = ConfigObj("/home/pi/zpc_ct/themes/" + self.mconf['theme'])
188
+		
189
+		self.light_grey = self.theme['light_grey']
190
+		self.apply_theme()
191
+		#self.config['title'] = "default"
192
+		#self.config['bpm'] = self.bpm
193
+		#self.config['volume'] = self.volume
194
+		#self.config.write()
195
+
196
+		#self.save_song()
197
+
198
+	class SoundSlot:
199
+		def __init__(self, file, obj_id, o):
200
+			self.file = file
201
+			self.id = obj_id
202
+			self.o = o
203
+			self.mixerSound = pygame.mixer.Sound(self.file)
204
+			self.og_sound = self.mixerSound
205
+			self.pattern = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]
206
+			self.notes = self.init_notes()
207
+			self.pitch = 0.0
208
+			self.volume = 16
209
+			self.start = 0
210
+			self.end = 0
211
+
212
+
213
+		def play(self, vol):
214
+			# snd_array = pygame.sndarray.array(self.mixerSound)
215
+			# snd_resample = resample(snd_array, 2.5, "sinc_fastest").astype(snd_array.dtype)
216
+			# snd_out = pygame.sndarray.make_sound(snd_resample)
217
+			# snd_out.play()
218
+			
219
+			if vol != 0:
220
+				vol = (vol / 16) * (self.volume / 16) * (self.o.volume / 16)
221
+				self.mixerSound.set_volume(vol)
222
+
223
+			pygame.mixer.Sound.play(self.mixerSound)  
224
+
225
+
226
+		def init_notes(self):
227
+			#print('initing notes')
228
+			outp = []
229
+			_id = 0
230
+			while _id < 64:
231
+				outp2 = []
232
+				_id2 = 0
233
+				while _id2 < 16:
234
+					outp2.append([0,16])
235
+					_id2 += 1
236
+				outp.append(outp2)	
237
+				_id += 1		
238
+
239
+			# for n in outp:
240
+			# 	print('sound ', self.id, ' ', n)
241
+			#print(outp)
242
+
243
+			return outp
244
+				
245
+
246
+	def Execute(self):
247
+		#print('doing the thing')
248
+		#self.check_buttons()
249
+		self.keys = keypad.pressed_keys
250
+		#self.key_flip()
251
+		self.update_keys()
252
+		self.FSM.Execute()
253
+		#self.disp.image(self.image, self.rotation)
254
+
255
+	def update_keys(self):
256
+		_id = 0 
257
+		for k in self.keyState:
258
+			if k == 0:
259
+				if _id in self.keys:
260
+					self.keyState[_id] = 1
261
+
262
+			elif k == 1:
263
+				if _id in self.keys:
264
+					self.keyState[_id] = 2
265
+				else:
266
+					self.keyState[_id] = 3
267
+
268
+			elif k == 2:
269
+				if _id in self.keys:
270
+					self.keyState[_id] = 2
271
+				else:
272
+					self.keyState[_id] = 3
273
+
274
+			else:
275
+				self.keyState[_id] = 0
276
+
277
+
278
+			_id += 1
279
+
280
+		if buttonA.value == 0:
281
+			#print('a on `', self.keyState[16])
282
+			if self.keyState[16] == 0: 
283
+				self.keyState[16] = 1
284
+			elif self.keyState[16] == 1:
285
+				self.keyState[16] = 2
286
+			else:
287
+				self.keyState[16] = 2
288
+		else:
289
+			if self.keyState[16] == 3:
290
+				self.keyState[16] = 4
291
+			elif self.keyState[16] == 2:
292
+				self.keyState[16] = 3
293
+			elif self.keyState[16] == 4:
294
+				self.keyState[16] = 0
295
+
296
+		if buttonB.value == 0:
297
+			#print('a on `', self.keyState[16])
298
+			if self.keyState[17] == 0: 
299
+				self.keyState[17] = 1
300
+			elif self.keyState[17] == 1:
301
+				self.keyState[17] = 2
302
+			else:
303
+				self.keyState[17] = 2
304
+		else:
305
+			if self.keyState[17] == 3:
306
+				self.keyState[17] = 4
307
+			elif self.keyState[17] == 2:
308
+				self.keyState[17] = 3
309
+			elif self.keyState[17] == 4:
310
+				self.keyState[17] = 0
311
+
312
+		# if buttonB.value == 0:
313
+		# 	#print('b on')
314
+		# 	if self.keyState[17] == 0 or self.keyState[17] == 1:
315
+		# 		self.keyState[17] += 1
316
+		# else:
317
+		# 	if self.keyState[17] == 2:
318
+		# 		self.keyState[17] += 1
319
+		# 	else:
320
+		# 		self.keyState[17] = 0
321
+
322
+	def update_bpm(self):
323
+		bpm = (60000 / self.sconf['bpm']) / 4
324
+		pygame.time.set_timer(TIMER, int(bpm))
325
+
326
+	def start_playback(self):
327
+		self.playing = True
328
+		self.playhead = -1
329
+		self.playhead = 0
330
+		#self.songCycle = cycle(self.song)
331
+		#self.curPattern = next(self.songCycle)
332
+		#self.songStart = self.curPattern
333
+
334
+		#self.curPattern = self.song[0]
335
+		#self.curPattern = self.songStart
336
+
337
+		self.songCycle = cycle(self.song)
338
+		self.curPattern = next(self.songCycle)
339
+		self.songStart = self.curPattern
340
+
341
+		bpm = (60000 / self.sconf.as_int('bpm')) / 4
342
+		pygame.time.set_timer(TIMER, int(bpm))
343
+		
344
+
345
+	def stop_playback(self):		
346
+		self.playing = False
347
+		self.playhead = -1
348
+		self.playhead = 0
349
+		self.curPattern = self.song[0]
350
+		#self.curPattern = self.songStart
351
+		pygame.time.set_timer(TIMER, 0)
352
+
353
+	def center_text(self, text, font, width, y, color):
354
+		w,h = font.getsize(text)
355
+		self.draw.text(((width-w)/2,(y-h)/2), text, font=font, fill=color)
356
+
357
+	def center_block(self, message, font, bounding_box, color):
358
+		x1, y1, x2, y2 = bounding_box  # For easy reading
359
+		w, h = self.draw.textsize(message, font=font)
360
+		x = (x2 - x1 - w)/2 + x1
361
+		y = (y2 - y1 - h)/2 + y1
362
+		self.draw.text((x, y), message, align='center', font=font, fill=color)
363
+		
364
+	def apply_theme(self):
365
+		self.light_grey = self.theme["light_grey"]
366
+		self.blue = self.theme["blue"]
367
+		self.pink = self.theme["pink"]
368
+		self.olive = self.theme["olive"]
369
+		self.black = self.theme["black"]
370
+		self.dark_blue = self.theme["dark_blue"]
371
+		self.green = self.theme["green"]
372
+		self.red = self.theme["red"]
373
+
374
+	def save_song(self):
375
+		base_dir = "/home/pi/zpc_ct/user/songs/"
376
+		#self.song_file_name
377
+		#file1 = open(base_dir + self.song_file_name,"w")#write mode
378
+		#file1.write("Tomorrow \n")
379
+		#file1.close()
380
+		self.sconf = ConfigObj(base_dir + self.song_file_name)
381
+		self.sconf['title'] = "default"
382
+		#self.config['bpm'] = self.bpm
383
+		self.sconf['volume'] = self.volume
384
+		sounds = []
385
+		notes = []
386
+		vols = []
387
+		pitches = []
388
+		for x in self.soundSlots:
389
+			sounds.append(x.file)
390
+			notes.append(x.notes)
391
+			vols.append(x.volume)
392
+			pitches.append(x.pitch)
393
+		self.sconf['sounds'] = sounds
394
+		self.sconf['notes'] = notes
395
+		self.sconf['song'] = self.song
396
+		#self.config['volumes'] = ['this', 'that', 'the other']
397
+		self.sconf.write()
398
+
399
+	def load_song(self):
400
+		base_dir = "/home/pi/zpc_ct/user/songs/"
401
+		self.sconf = ConfigObj(base_dir + 'default.sng')
402
+		one = self.sconf['song']
403
+		#two = ast.literal_eval(self.sconf['song'])
404
+		three = self.sconf.as_list('song')
405
+		#four = ast.literal_eval(self.sconf.as_list('song'))
406
+		song = []
407
+		for i in self.sconf['song']:
408
+			song.append(int(i))
409
+		#lst = ast.literal_eval(self.sconf['song'][0])
410
+		self.song = song
411
+		print('this is the song')
412
+		#print(lst)
413
+		print(song)
414
+		#print(two)
415
+		#print(three)
416
+		#print(four)
417
+		#print(five)
418
+
419
+		self.songCycle = cycle(self.song)
420
+		self.curPattern = next(self.songCycle)
421
+		self.songStart = self.curPattern
422
+		#self.config = ConfigObj(base_dir + self.song_file_name)
423
+		#print('these sounds should get loaded ', self.config['sounds'])
424
+
425
+
426
+p = Prog()
427
+
428
+#while True:
429
+	
430
+while not done:
431
+	p.Execute()
432
+	if pygame.event.get(pygame.USEREVENT + 1):
433
+		
434
+		if p.playhead == 0:
435
+			print('pattern ', p.curPattern)
436
+		p.playhead += 1
437
+
438
+		if p.playhead == 16:
439
+			p.playhead = 0
440
+			#self.draw.text((0, 0), str(self.playhead), font=font, fill="#FFFFFF")
441
+
442
+			#self.disp.image(self.image, self.rotation)
443
+			p.curPattern = next(p.songCycle)
444
+			#print('pattern ', p.curPattern)
445
+
446
+		if p.playhead  % 8 == 0:
447
+			GPIO.setup(47, GPIO.OUT)
448
+			GPIO.output(47, GPIO.LOW)
449
+			#print('led on')
450
+		else:
451
+			GPIO.setup(47, GPIO.OUT)
452
+			GPIO.output(47, GPIO.HIGH)
453
+			#print('led off')
454
+		p.pub.dispatch('beat', [p.curPattern, p.playhead]) 
455
+	#clock.tick(240)
456
+	clock.tick_busy_loop()
457
+	#print(clock.get_fps())
458
+
459
+pygame.quit()
460
+
461
+# 1 main
462
+# 2 select pattern
463
+# 3 note edit
464
+# 6 song edit
465
+# 7 program load
466
+# 8 song load
467
+# 10 utility
468
+# 11 bpm+
469
+# 12 bpm-
470
+# 13 play
471
+# 14 stop
472
+# 15 vol+
473
+# 16 vol-

+ 21
- 0
scripts/blinka_test.py View File

@@ -0,0 +1,21 @@
1
+import board
2
+import digitalio
3
+import busio
4
+
5
+print("Hello blinka!")
6
+
7
+# Try to great a Digital input
8
+pin = digitalio.DigitalInOut(board.D4)
9
+print("Digital IO ok!")
10
+
11
+# Try to create an I2C device
12
+i2c = busio.I2C(board.SCL, board.SDA)
13
+print("I2C ok!")
14
+
15
+# Try to create an SPI device
16
+spi = busio.SPI(board.SCLK, board.MOSI, board.MISO)
17
+print("SPI ok!")
18
+
19
+print("done!")
20
+
21
+print(dir(board.pin))

+ 48
- 0
scripts/display_test.py View File

@@ -0,0 +1,48 @@
1
+# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
2
+# SPDX-License-Identifier: MIT
3
+
4
+import digitalio
5
+import board
6
+
7
+from adafruit_rgb_display.rgb import color565
8
+import adafruit_rgb_display.st7789 as st7789
9
+
10
+# Configuration for CS and DC pins for Raspberry Pi
11
+cs_pin = digitalio.DigitalInOut(board.CE0)
12
+dc_pin = digitalio.DigitalInOut(board.D25)
13
+reset_pin = None
14
+BAUDRATE = 64000000  # The pi can be very fast!
15
+# Create the ST7789 display:
16
+display = st7789.ST7789(
17
+    board.SPI(),
18
+    cs=cs_pin,
19
+    dc=dc_pin,
20
+    rst=reset_pin,
21
+    baudrate=BAUDRATE,
22
+    width=135,
23
+    height=240,
24
+    x_offset=53,
25
+    y_offset=40,
26
+)
27
+
28
+backlight = digitalio.DigitalInOut(board.D22)
29
+backlight.switch_to_output()
30
+backlight.value = True
31
+buttonA = digitalio.DigitalInOut(board.D23)
32
+buttonB = digitalio.DigitalInOut(board.D24)
33
+buttonA.switch_to_input()
34
+buttonB.switch_to_input()
35
+
36
+# Main loop:
37
+while True:
38
+    if buttonA.value and buttonB.value:
39
+        backlight.value = False  # turn off backlight
40
+    else:
41
+        backlight.value = True  # turn on backlight
42
+    if buttonB.value and not buttonA.value:  # just button A pressed
43
+        display.fill(color565(255, 0, 0))  # red
44
+    if buttonA.value and not buttonB.value:  # just button B pressed
45
+        display.fill(color565(0, 0, 255))  # blue
46
+    if not buttonA.value and not buttonB.value:  # none pressed
47
+        display.fill(color565(0, 255, 0))  # green
48
+

+ 21
- 0
scripts/map.py View File

@@ -0,0 +1,21 @@
1
+import microcontroller
2
+import board
3
+
4
+board_pins = []
5
+for pin in dir(microcontroller.Pin):
6
+    if isinstance(getattr(microcontroller.Pin, pin), microcontroller.Pin):
7
+        pins = []
8
+        for alias in dir(board):
9
+            if getattr(board, alias) is getattr(microcontroller.Pin, pin):
10
+                pins.append("board.{}".format(alias))
11
+        if len(pins) > 0:
12
+            board_pins.append(" ".join(pins))
13
+for pins in sorted(board_pins):
14
+    print(pins)
15
+
16
+print('done')
17
+
18
+
19
+
20
+for pin in dir(microcontroller.Pin):
21
+    print(pin)

+ 12
- 0
scripts/metro.py View File

@@ -0,0 +1,12 @@
1
+from time import sleep, perf_counter
2
+
3
+delay = d = 0.7
4
+print(60 / delay, 'bpm')
5
+prev = perf_counter()
6
+for i in range(20):
7
+    sleep(d)
8
+    t = perf_counter()
9
+    delta = t - prev - delay
10
+    print('{:+.9f}'.format(delta))
11
+    d -= delta
12
+    prev = t

+ 396
- 0
scripts/test.py View File

@@ -0,0 +1,396 @@
1
+from pygame import mixer
2
+import time
3
+import digitalio
4
+import board
5
+import adafruit_matrixkeypad
6
+import pygame
7
+from PIL import Image, ImageDraw, ImageFont
8
+import adafruit_rgb_display.st7789 as st7789
9
+import digitalio
10
+import board
11
+from adafruit_rgb_display.rgb import color565
12
+import adafruit_rgb_display.st7789 as st7789
13
+import lib.FSM
14
+from itertools import cycle
15
+import lib.observer
16
+from scikits.samplerate import resample
17
+from lib.configobj import ConfigObj
18
+#from configobj import ConfigObj
19
+
20
+
21
+
22
+cols = [digitalio.DigitalInOut(x) for x in (board.D21, board.D20, board.D16, board.D12)]
23
+rows = [digitalio.DigitalInOut(x) for x in (board.D26, board.D13, board.D6, board.D5)]
24
+# keys = ((3, 2, 1, 0),
25
+#         (7, 6, 5, 4),
26
+#         (11, 10, 9, 8),
27
+#         (15, 14, 13, 12))
28
+
29
+keys = ((15, 14, 13, 12),
30
+		(11, 10, 9, 8),
31
+		(7, 6, 5, 4),
32
+		(3, 2, 1, 0))
33
+
34
+
35
+keypad = adafruit_matrixkeypad.Matrix_Keypad(rows, cols, keys)
36
+
37
+# while True:
38
+#     keys = keypad.pressed_keys
39
+#     if keys:
40
+#         print("Pressed: ", keys)
41
+#     time.sleep(0.1)
42
+
43
+
44
+
45
+
46
+pygame.init()
47
+mixer.init()
48
+done = False
49
+clock = pygame.time.Clock()
50
+TIMER = pygame.USEREVENT + 1
51
+#pygame.time.set_timer(pygame.USEREVENT + 1, 444)
52
+#pygame.time.set_timer(TIMER, 161)
53
+playhead = 0
54
+
55
+timer = pygame.time.get_ticks
56
+start = now = timer()
57
+
58
+cs_pin = digitalio.DigitalInOut(board.CE0)
59
+dc_pin = digitalio.DigitalInOut(board.D25)
60
+reset_pin = None
61
+BAUDRATE = 64000000  # The pi can be very fast!
62
+# display = st7789.ST7789(
63
+# 	board.SPI(),
64
+# 	cs=cs_pin,
65
+# 	dc=dc_pin,
66
+# 	rst=reset_pin,
67
+# 	baudrate=BAUDRATE,
68
+# 	width=135,
69
+# 	height=240,
70
+# 	x_offset=53,
71
+# 	y_offset=40,
72
+# )
73
+
74
+backlight = digitalio.DigitalInOut(board.D22)
75
+backlight.switch_to_output()
76
+backlight.value = True
77
+buttonA = digitalio.DigitalInOut(board.D23)
78
+buttonB = digitalio.DigitalInOut(board.D24)
79
+buttonA.switch_to_input()
80
+buttonB.switch_to_input()
81
+
82
+spi = board.SPI()
83
+
84
+
85
+
86
+# Create blank image for drawing.
87
+# Make sure to create image with mode 'RGB' for full color.
88
+
89
+# Get drawing object to draw on image.
90
+
91
+
92
+# Draw a black filled box to clear the image.
93
+#draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
94
+#disp.image(image, rotation)
95
+# Draw some shapes.
96
+# First define some constants to allow easy resizing of shapes.
97
+
98
+# Move left to right keeping track of the current x position for drawing shapes.
99
+x = 0
100
+
101
+
102
+
103
+# Turn on the backlight
104
+backlight = digitalio.DigitalInOut(board.D22)
105
+backlight.switch_to_output()
106
+backlight.value = True
107
+
108
+
109
+class Prog:
110
+	def __init__(self):
111
+		self.FSM = FSM.ProgFSM(self)
112
+		self.font = ImageFont.truetype("/home/pi/examples/HLM.ttf", 64)
113
+		#self.h1 = ImageFont.truetype("/home/pi/examples/HLM.ttf", 26)
114
+		self.h1 = ImageFont.truetype("/home/pi/Pixellari.ttf", 30)
115
+		self.h2 = ImageFont.truetype("/home/pi/Pixellari.ttf", 20)
116
+		self.h3 = ImageFont.truetype("/home/pi/Pixellari.ttf", 54)
117
+		
118
+		self.disp = st7789.ST7789(
119
+			spi,
120
+			cs=cs_pin,
121
+			dc=dc_pin,
122
+			rst=reset_pin,
123
+			baudrate=BAUDRATE,
124
+			width=135,
125
+			height=240,
126
+			x_offset=53,
127
+			y_offset=40,
128
+		)
129
+
130
+		self.height = self.disp.width  # we swap height/width to rotate it to landscape!
131
+		self.width = self.disp.height
132
+
133
+		self.rotation = 270
134
+		self.padding = -2
135
+		self.top = self.padding
136
+		self.bottom = self.height - self.padding
137
+
138
+		self.image = Image.new("RGB", (self.width, self.height))
139
+		self.draw = ImageDraw.Draw(self.image)
140
+		self.playhead = 0
141
+		self.playing = False
142
+		self.bpm = 90
143
+		self.bpm_inc = 1
144
+		self.note_bank = 0
145
+		self.volume = 10
146
+		self.note_vol = 16
147
+		self.note_pitch = 0.0
148
+		self.soundSlots = []
149
+		self.keys = []
150
+		self.keyState = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
151
+		#self.song = [2, 3, 0, 1, 0, 1, 0, 1, 0, 1]
152
+		self.song = [0]
153
+		self.songCycle = cycle(self.song)
154
+		self.curPattern = next(self.songCycle)
155
+		self.songStart = self.curPattern
156
+		self.eSound = 0
157
+		self.ePattern = 0
158
+		self.black = "#000000"
159
+		self.bg_color = "#336699"
160
+		self.color_a = "#FFFFFF"
161
+		self.color_b = "#929230"
162
+		self.color_c = "#FFFF00"
163
+		self.dark_grey = "#404040"
164
+		self.grey = "#808080"
165
+		self.light_grey = "#D3D3D3"
166
+		self.blue = "#2A80D5"
167
+		self.dark_blue = "#2A2AD5"
168
+		self.pink = "#D52A80"
169
+		self.red = "#D52A2A"
170
+		self.olive = "#80D52A"
171
+		self.green = "#2AD52A"
172
+
173
+		self.pub = observer.Publisher(['beat', 'the joint'])
174
+
175
+		self.song_file_name = "default.sng"
176
+		self.config = ConfigObj("config.txt")
177
+		self.config['title'] = "default"
178
+		self.config['bpm'] = self.bpm
179
+		self.config['volume'] = self.volume
180
+		self.config.write()
181
+
182
+		#self.save_song()
183
+
184
+	class SoundSlot:
185
+		def __init__(self, file, obj_id, o):
186
+			self.file = file
187
+			self.id = obj_id
188
+			self.o = o
189
+			self.mixerSound = pygame.mixer.Sound(self.file)
190
+			self.pattern = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]
191
+			self.notes = self.init_notes()
192
+			self.pitch = 0.0
193
+			self.volume = 16
194
+
195
+
196
+		def play(self, vol, pitch):
197
+			# snd_array = pygame.sndarray.array(self.mixerSound)
198
+			# snd_resample = resample(snd_array, 2.5, "sinc_fastest").astype(snd_array.dtype)
199
+			# snd_out = pygame.sndarray.make_sound(snd_resample)
200
+			# snd_out.play()
201
+			
202
+			if vol != 0:
203
+				vol = (vol / 16) * (self.volume / 16) * (self.o.volume / 16)
204
+				self.mixerSound.set_volume(vol)
205
+
206
+			pygame.mixer.Sound.play(self.mixerSound)  
207
+
208
+
209
+		def init_notes(self):
210
+			outp = []
211
+			_id = 0
212
+			while _id < 64:
213
+				outp2 = []
214
+				_id2 = 0
215
+				while _id2 < 64:
216
+					outp2.append([0,16,0])
217
+					_id2 += 1
218
+				outp.append(outp2)	
219
+				_id += 1		
220
+
221
+			# for n in outp:
222
+			# 	print('sound ', self.id, ' ', n)
223
+			#print(outp)
224
+
225
+			return outp
226
+				
227
+
228
+	def Execute(self):
229
+		#print('doing the thing')
230
+		#self.check_buttons()
231
+		self.keys = keypad.pressed_keys
232
+		#self.key_flip()
233
+		self.update_keys()
234
+		self.FSM.Execute()
235
+		#self.disp.image(self.image, self.rotation)
236
+
237
+	def update_keys(self):
238
+		_id = 0 
239
+		for k in self.keyState:
240
+			if k == 0:
241
+				if _id in self.keys:
242
+					self.keyState[_id] = 1
243
+
244
+			elif k == 1:
245
+				if _id in self.keys:
246
+					self.keyState[_id] = 2
247
+				else:
248
+					self.keyState[_id] = 3
249
+
250
+			elif k == 2:
251
+				if _id in self.keys:
252
+					self.keyState[_id] = 2
253
+				else:
254
+					self.keyState[_id] = 3
255
+
256
+			else:
257
+				self.keyState[_id] = 0
258
+
259
+
260
+			_id += 1
261
+
262
+		if buttonA.value == 0:
263
+			#print('a on `', self.keyState[16])
264
+			if self.keyState[16] == 0: 
265
+				self.keyState[16] = 1
266
+			elif self.keyState[16] == 1:
267
+				self.keyState[16] = 2
268
+			else:
269
+				self.keyState[16] = 2
270
+		else:
271
+			if self.keyState[16] == 3:
272
+				self.keyState[16] = 4
273
+			elif self.keyState[16] == 2:
274
+				self.keyState[16] = 3
275
+			elif self.keyState[16] == 4:
276
+				self.keyState[16] = 0
277
+
278
+		if buttonB.value == 0:
279
+			#print('a on `', self.keyState[16])
280
+			if self.keyState[17] == 0: 
281
+				self.keyState[17] = 1
282
+			elif self.keyState[17] == 1:
283
+				self.keyState[17] = 2
284
+			else:
285
+				self.keyState[17] = 2
286
+		else:
287
+			if self.keyState[17] == 3:
288
+				self.keyState[17] = 4
289
+			elif self.keyState[17] == 2:
290
+				self.keyState[17] = 3
291
+			elif self.keyState[17] == 4:
292
+				self.keyState[17] = 0
293
+
294
+		# if buttonB.value == 0:
295
+		# 	#print('b on')
296
+		# 	if self.keyState[17] == 0 or self.keyState[17] == 1:
297
+		# 		self.keyState[17] += 1
298
+		# else:
299
+		# 	if self.keyState[17] == 2:
300
+		# 		self.keyState[17] += 1
301
+		# 	else:
302
+		# 		self.keyState[17] = 0
303
+
304
+	def update_bpm(self):
305
+		bpm = (60000 / self.bpm) / 4
306
+		pygame.time.set_timer(TIMER, int(bpm))
307
+
308
+	def start_playback(self):
309
+		self.playing = True
310
+		self.playhead = -1
311
+		self.curPattern = self.songStart
312
+		bpm = (60000 / self.bpm) / 4
313
+		pygame.time.set_timer(TIMER, int(bpm))
314
+		
315
+
316
+	def stop_playback(self):		
317
+		self.playing = False
318
+		self.playhead = -1
319
+		self.curPattern = self.songStart
320
+		pygame.time.set_timer(TIMER, 0)
321
+
322
+	def center_text(self, text, font, width, y, color):
323
+		w,h = font.getsize(text)
324
+		self.draw.text(((width-w)/2,(y-h)/2), text, font=font, fill=color)
325
+
326
+	def center_block(self, message, font, bounding_box, color):
327
+		x1, y1, x2, y2 = bounding_box  # For easy reading
328
+		w, h = self.draw.textsize(message, font=font)
329
+		x = (x2 - x1 - w)/2 + x1
330
+		y = (y2 - y1 - h)/2 + y1
331
+		self.draw.text((x, y), message, align='center', font=font, fill=color)
332
+		
333
+
334
+	def save_song(self):
335
+		base_dir = "/home/pi/zpc_ct/user/songs/"
336
+		#self.song_file_name
337
+		#file1 = open(base_dir + self.song_file_name,"w")#write mode
338
+		#file1.write("Tomorrow \n")
339
+		#file1.close()
340
+		self.config = ConfigObj(base_dir + self.song_file_name)
341
+		self.config['title'] = "default"
342
+		self.config['bpm'] = self.bpm
343
+		self.config['volume'] = self.volume
344
+		sounds = []
345
+		notes = []
346
+		for x in self.soundSlots:
347
+			sounds.append(x.file)
348
+			notes.append(x.notes)
349
+		self.config['sounds'] = sounds
350
+		self.config['notes'] = notes
351
+		self.config['volumes'] = ['this', 'that', 'the other']
352
+		self.config.write()
353
+
354
+	def load_song(self):
355
+		base_dir = "/home/pi/zpc_ct/user/songs/"
356
+		self.config = ConfigObj(base_dir + 'r100.sng')
357
+		#self.config = ConfigObj(base_dir + self.song_file_name)
358
+		#print('these sounds should get loaded ', self.config['sounds'])
359
+
360
+
361
+p = Prog()
362
+
363
+#while True:
364
+	
365
+while not done:
366
+	p.Execute()
367
+	if pygame.event.get(pygame.USEREVENT + 1):
368
+		p.playhead += 1
369
+
370
+		if p.playhead == 16:
371
+			p.playhead = 0
372
+			#self.draw.text((0, 0), str(self.playhead), font=font, fill="#FFFFFF")
373
+
374
+			#self.disp.image(self.image, self.rotation)
375
+			p.curPattern = next(p.songCycle)
376
+			print('pattern ', p.curPattern)
377
+		p.pub.dispatch('beat', [p.curPattern, p.playhead]) 
378
+	#clock.tick(240)
379
+	clock.tick_busy_loop()
380
+	#print(clock.get_fps())
381
+
382
+pygame.quit()
383
+
384
+# 1 main
385
+# 2 select pattern
386
+# 3 note edit
387
+# 6 song edit
388
+# 7 program load
389
+# 8 song load
390
+# 10 utility
391
+# 11 bpm+
392
+# 12 bpm-
393
+# 13 play
394
+# 14 stop
395
+# 15 vol+
396
+# 16 vol-

+ 3
- 0
scripts/timer.py View File

@@ -0,0 +1,3 @@
1
+import pygame
2
+
3
+

+ 14
- 0
themes/dark.thm View File

@@ -0,0 +1,14 @@
1
+#main text
2
+light_grey = "#D3D3D3"
3
+#main background
4
+blue = "#000000"
5
+#highlight1
6
+pink = "#D52A80"
7
+#highlight2
8
+olive = "#80D52A"
9
+black = "#000000"
10
+dark_grey = "#404040"
11
+grey = "#808080"
12
+dark_blue = "#2A2AD5"
13
+red = "#D52A2A"
14
+green = "#2AD52A"

+ 14
- 0
themes/default.thm View File

@@ -0,0 +1,14 @@
1
+#main text
2
+light_grey = "#D3D3D3"
3
+#main background
4
+blue = "#336699"
5
+#highlight1
6
+pink = "#D52A80"
7
+#highlight2
8
+olive = "#80D52A"
9
+black = "#000000"
10
+dark_grey = "#404040"
11
+grey = "#808080"
12
+dark_blue = "#2A2AD5"
13
+red = "#D52A2A"
14
+green = "#2AD52A"

+ 7
- 0
user/songs/_blank.sng
File diff suppressed because it is too large
View File


+ 7
- 0
user/songs/default.sng
File diff suppressed because it is too large
View File


+ 6
- 0
user/songs/r100.sng
File diff suppressed because it is too large
View File


Loading…
Cancel
Save