raspberry pi zero based drum machine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.py 13KB

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