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

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