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 12KB

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