import sys sys.path.insert(0, './lib') from pygame import mixer import time import digitalio import board import adafruit_matrixkeypad import pygame from PIL import Image, ImageDraw, ImageFont #import adafruit_rgb_display.st7789 as st7789 #import digitalio #import board from adafruit_rgb_display.rgb import color565 import adafruit_rgb_display.st7789 as st7789 import FSM from itertools import cycle import observer from scikits.samplerate import resample from configobj import ConfigObj import json import ast import RPi.GPIO as GPIO #from time import sleep import threading import os GPIO.setmode(GPIO.BCM) GPIO.setup(29, GPIO.OUT) #internal led GPIO output channel cols = [digitalio.DigitalInOut(x) for x in (board.D21, board.D20, board.D16, board.D12)] rows = [digitalio.DigitalInOut(x) for x in (board.D26, board.D13, board.D6, board.D5)] keys = ((15, 14, 13, 12), (11, 10, 9, 8), (7, 6, 5, 4), (3, 2, 1, 0)) keypad = adafruit_matrixkeypad.Matrix_Keypad(rows, cols, keys) pygame.init() done = False clock = pygame.time.Clock() TIMER = pygame.USEREVENT + 1 playhead = 0 timer = pygame.time.get_ticks start = now = timer() cs_pin = digitalio.DigitalInOut(board.CE0) dc_pin = digitalio.DigitalInOut(board.D25) reset_pin = None BAUDRATE = 64000000 backlight = digitalio.DigitalInOut(board.D22) backlight.switch_to_output() backlight.value = True buttonA = digitalio.DigitalInOut(board.D23) buttonB = digitalio.DigitalInOut(board.D24) buttonA.switch_to_input() buttonB.switch_to_input() spi = board.SPI() x = 0 # Turn on the backlight backlight = digitalio.DigitalInOut(board.D22) backlight.switch_to_output() backlight.value = True class Prog: def __init__(self): self.FSM = FSM.ProgFSM(self) #self.start_mixer() #self.font = ImageFont.truetype("/home/pi/examples/HLM.ttf", 64) #self.h1 = ImageFont.truetype("/home/pi/examples/HLM.ttf", 26) self.h1 = ImageFont.truetype("/home/pi/zpc_ct/fonts/Pixellari.ttf", 30) self.h2 = ImageFont.truetype("/home/pi/zpc_ct/fonts/Pixellari.ttf", 20) self.h3 = ImageFont.truetype("/home/pi/zpc_ct/fonts/Pixellari.ttf", 54) self.h4 = ImageFont.truetype("/home/pi/zpc_ct/fonts/Pixellari.ttf", 72) self.disp = st7789.ST7789( spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE, width=135, height=240, x_offset=53, y_offset=40, ) self.height = self.disp.width # we swap height/width to rotate it to landscape! self.width = self.disp.height self.rotation = 270 self.padding = -2 self.top = self.padding self.bottom = self.height - self.padding self.image = Image.new("RGB", (self.width, self.height)) self.draw = ImageDraw.Draw(self.image) self.playhead = 0 self.playing = False self.bpm = 90 self.bpm_inc = 1 self.half_bpm = 0.0 self.note_bank = 0 self.pat_bank = 0 self.volume = 10 self.note_vol = 16 self.note_pitch = 0.0 self.soundSlots = [] self.keys = [] self.keyState = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] self.song = [0] self.songCycle = cycle(self.song) self.curPattern = next(self.songCycle) self.songStart = self.curPattern self.eSound = 0 self.ePattern = 0 self.patternFollow = False self.patternClipboard = [] self.soundClipboard = [] self.last_load_sound_id = None self.odub = False self.undo_buf = [] self.erase_buf = [] self.erase = False self.mute_buf = [] self.mute = False self.black = "#000000" self.bg_color = "#336699" self.color_b = "#929230" self.dark_grey = "#404040" self.grey = "#808080" self.light_grey = "#D3D3D3" self.blue = "#336699" self.dark_blue = "#2A2AD5" self.pink = "#D52A80" self.olive = "#80D52A" self.notes_on = [] self.clear_notes_on() self.prev = True self.pat_odub = False self.pat_perf = False self.pat_buf = None self.song_in = [] self.press_ticks_up = None self.press_ticks_down = None self.pub = observer.Publisher(['beat', 'the joint']) self.repeater_states = [] _iter = 0 while _iter < 18: self.repeater_states.append(None) _iter += 1 self.mconf = ConfigObj("config.txt") self.sconf = ConfigObj("/home/pi/zpc_ct/user/songs/" + self.mconf['default_song']) self.theme = ConfigObj("/home/pi/zpc_ct/user/themes/" + self.mconf['theme'] + ".thm") self.load_sound_dir = '/home/pi/zpc_ct/user/sounds/' self.song_dir = self.mconf['song_dir'] self.base_song_dir = '/home/pi/zpc_ct/user/songs/' self.light_grey = self.theme['light_grey'] self.apply_theme() self.display_thread = threading.Thread(target=self.update_display, args=([0])) self.defender_sounds = [] self.load_defender() self.wifi_ssid = '' self.wifi_pass = '' class SoundSlot: def __init__(self, file, obj_id, o): self.file = file self.id = obj_id self.o = o self.mixerSound = None self.soundArray = None self.og_sound = None if file != None: self.create_sound() self.pitch = 0 self.volume = 16 self.start = 0 self.end = 0 def create_sound(self): self.mixerSound = pygame.mixer.Sound(self.file) self.soundArray = pygame.sndarray.array(self.mixerSound) self.og_sound = self.mixerSound def play(self, vol): if self.file != None: if vol != 0: vol = (vol / 16) * (self.volume / 16) * (self.o.volume / 16) self.mixerSound.set_volume(vol) pygame.mixer.Sound.play(self.mixerSound) def set_pitch(self): if self.pitch == 0: self.mixerSound = self.og_sound else: snd_resample = resample(self.soundArray, ((self.pitch * - 1) / 10) + 1, "sinc_fastest").astype(self.soundArray.dtype) self.mixerSound = pygame.sndarray.make_sound(snd_resample) def init_notes(self): outp = [] _id = 0 while _id < 64: outp2 = [] _id2 = 0 while _id2 < 16: outp2.append([0,16]) _id2 += 1 outp.append(outp2) _id += 1 return outp def init_slots(self): _iter = 0 self.slots = [] while _iter < 64: p1 = self.SoundSlot(None, _iter, self) self.slots.append(p1) _iter += 1 def Execute(self): self.keys = keypad.pressed_keys self.update_keys() self.FSM.Execute() def update_keys(self): _id = 0 for k in self.keyState: if k == 0: if _id in self.keys: self.keyState[_id] = 1 elif k == 1: if _id in self.keys: self.keyState[_id] = 2 else: self.keyState[_id] = 3 elif k == 2: if _id in self.keys: self.keyState[_id] = 2 else: self.keyState[_id] = 3 else: self.keyState[_id] = 0 _id += 1 if buttonA.value == 0: if self.keyState[16] == 0: self.keyState[16] = 1 elif self.keyState[16] == 1: self.keyState[16] = 2 else: self.keyState[16] = 2 else: if self.keyState[16] == 3: self.keyState[16] = 4 elif self.keyState[16] == 2: self.keyState[16] = 3 elif self.keyState[16] == 4: self.keyState[16] = 0 if buttonB.value == 0: if self.keyState[17] == 0: self.keyState[17] = 1 elif self.keyState[17] == 1: self.keyState[17] = 2 else: self.keyState[17] = 2 else: if self.keyState[17] == 3: self.keyState[17] = 4 elif self.keyState[17] == 2: self.keyState[17] = 3 elif self.keyState[17] == 4: self.keyState[17] = 0 def button_repeater(self, state, enables): if state == 0: pass elif state == 1: for i in self.repeater_states: i = None def update_bpm(self): bpm = (60000 / self.bpm) / 4 self.half_bpm = bpm / 2 if self.playing: pygame.time.set_timer(TIMER, int(bpm)) def start_playback(self): if len(self.song) > 0: self.playing = True self.playhead = -1 self.playhead = 0 self.songCycle = cycle(self.song) self.curPattern = next(self.songCycle) self.songStart = self.curPattern bpm = (60000 / self.bpm) / 4 pygame.time.set_timer(TIMER, int(bpm)) GPIO.setup(29, GPIO.OUT) GPIO.output(29, GPIO.LOW) def stop_playback(self): self.playing = False self.playhead = -1 self.playhead = 0 if len(self.song) > 0: self.curPattern = self.song[0] else: self.curPattern = 0 pygame.time.set_timer(TIMER, 0) GPIO.setup(29, GPIO.OUT) GPIO.output(29, GPIO.HIGH) if self.pat_odub: print('stopping with odub') self.pat_perf = False self.pat_odub = False self.song = self.song_in self.pat_buf = self.song_in self.song_in = [] self.songCycle = cycle(self.song) self.songStart = self.song[0] def center_text(self, text, font, width, y, color): w,h = font.getsize(text) self.draw.text(((width-w)/2,(y-h)/2), text, font=font, fill=color) def center_block(self, message, font, bounding_box, color): x1, y1, x2, y2 = bounding_box w, h = self.draw.textsize(message, font=font) x = (x2 - x1 - w)/2 + x1 y = (y2 - y1 - h)/2 + y1 self.draw.text((x, y), message, align='center', font=font, fill=color) def apply_theme(self): self.grey = self.theme["grey"] self.light_grey = self.theme["light_grey"] self.dark_grey = self.theme["dark_grey"] self.blue = self.theme["blue"] self.pink = self.theme["pink"] self.olive = self.theme["olive"] def save_song(self): base_dir = self.song_dir title = self.sconf['title'] fname = base_dir + self.sconf['title'] + '.sng' if os.path.exists(fname): os.utime(fname, None) else: open(fname, 'a').close() self.sconf = ConfigObj(fname) self.sconf['volume'] = self.volume self.sconf['song'] = self.song self.sconf['bpm'] = self.bpm self.sconf['title'] = title notes = [] for n in self.soundSlots: notes.append([n.id, n.file, n.volume, n.pitch, n.notes]) self.sconf['sounds'] = notes self.sconf.write() def save_config(self): base_dir = "/home/pi/zpc_ct/" self.mconf['song_dir'] = self.song_dir self.mconf.write() def load_config(self): base_dir = "/home/pi/zpc_ct/" self.sconf = ConfigObj(base_dir + 'config.txt') self.theme = ConfigObj("/home/pi/zpc_ct/user/themes/" + self.mconf['theme'] + ".thm") self.song_dir = self.mconf['song_dir'] def load_song(self): self.quit_mixer() self.start_mixer() base_dir = self.song_dir self.sconf = ConfigObj(base_dir + self.mconf['default_song'] + '.sng') print(base_dir + self.mconf['default_song'] + '.sng') self.last_load_sound_id = None print('song bpm is ', self.sconf['bpm']) self.bpm = self.sconf.as_int('bpm') song = [] for i in self.sconf['song']: song.append(int(i)) obj_id = 0 self.soundSlots = [] _iter = 0 while _iter < 64: snd = self.sconf.as_list('sounds')[_iter] snd_lst = ast.literal_eval(snd) obj_id = snd_lst[0] filename = snd_lst[1] volume = int(snd_lst[2]) pitch = int(snd_lst[3]) notes = snd_lst[4] p1 = self.SoundSlot(filename, obj_id, self) p1.volume = volume p1.pitch = pitch if pitch != 0: p1.set_pitch() self.soundSlots.append(p1) p1.notes = notes _iter += 1 self.song = song self.songCycle = cycle(self.song) self.curPattern = next(self.songCycle) self.songStart = self.curPattern self.update_bpm() def update_display(self, x): try: self.disp.image(self.image, self.rotation) except: print('display failed') def ud(self, x): self.disp.image(self.image, self.rotation) def clear_notes_on(self): _iter = 0 self.notes_on = [] while _iter < 64: self.notes_on.append(0) _iter += 1 def start_mixer(self): print('starting mixer') mixer.init() mixer.set_num_channels(32) def quit_mixer(self): if mixer.get_init() != None: print('quitting mixer') mixer.quit() def load_defender(self): s1 = pygame.mixer.Sound('/home/pi/zpc_ct/user/sounds/po-arc/05.wav') self.defender_sounds.append(s1) s2 = pygame.mixer.Sound('/home/pi/zpc_ct/user/sounds/po-arc/08.wav') self.defender_sounds.append(s2) s3 = pygame.mixer.Sound('/home/pi/zpc_ct/user/sounds/po-arc/11.wav') self.defender_sounds.append(s3) p = Prog() while not done: p.Execute() if pygame.event.get(pygame.USEREVENT + 1): p.playhead += 1 if p.playhead == 16: p.playhead = 0 p.curPattern = next(p.songCycle) #blink internal led if p.playhead % 4 == 0: GPIO.setup(29, GPIO.OUT) GPIO.output(29, GPIO.LOW) else: GPIO.setup(29, GPIO.OUT) GPIO.output(29, GPIO.HIGH) p.pub.dispatch('beat', [p.curPattern, p.playhead]) clock.tick_busy_loop() #print(clock.get_fps()) pygame.quit()