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) # set up GPIO output channel - internal led GPIO.setup(29, GPIO.OUT) 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() # mixer.init() # mixer.set_num_channels(32) 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 # The pi can be very fast! #BAUDRATE = 24000000 # display = st7789.ST7789( # board.SPI(), # cs=cs_pin, # dc=dc_pin, # rst=reset_pin, # baudrate=BAUDRATE, # width=135, # height=240, # x_offset=53, # y_offset=40, # ) 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() # Create blank image for drawing. # Make sure to create image with mode 'RGB' for full color. # Get drawing object to draw on image. # Draw a black filled box to clear the image. #draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0)) #disp.image(image, rotation) # Draw some shapes. # First define some constants to allow easy resizing of shapes. # Move left to right keeping track of the current x position for drawing shapes. 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.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.slots = [] self.keys = [] self.keyState = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] #self.song = [2, 3, 0, 1, 0, 1, 0, 1, 0, 1] 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.black = "#000000" self.bg_color = "#336699" #self.color_a = "#FFFFFF" self.color_b = "#929230" #self.color_c = "#FFFF00" self.dark_grey = "#404040" self.grey = "#808080" self.light_grey = "#D3D3D3" self.blue = "#336699" self.dark_blue = "#2A2AD5" self.pink = "#D52A80" #self.red = "#D52A2A" self.olive = "#80D52A" #self.green = "#2AD52A" self.notes_on = [] self.clear_notes_on() self.pub = observer.Publisher(['beat', 'the joint']) #self.song_file_name = "default.sng" 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_song_dir = '/home/pi/zpc_ct/user/sounds/' self.light_grey = self.theme['light_grey'] self.apply_theme() self.display_thread = threading.Thread(target=self.update_display, args=([0])) 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.mixerSound = pygame.mixer.Sound(self.file) #self.soundArray = pygame.sndarray.array(self.mixerSound) #self.og_sound = self.mixerSound self.create_sound() #self.pattern = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0] #self.notes = self.init_notes() 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): # snd_array = pygame.sndarray.array(self.mixerSound) # snd_resample = resample(snd_array, 2.5, "sinc_fastest").astype(snd_array.dtype) # snd_out = pygame.sndarray.make_sound(snd_resample) # snd_out.play() #lst = ast.literal_eval(self.o.sconf.as_list('volumes')[self.id]) #print('---------- ', 'this is a volume') #print(lst) #print('note vol ', self.volume) #o.sconf 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) # snd_array = pygame.sndarray.array(self.mixerSound) print('setting pitch to ', self.pitch) def init_notes(self): #print('initing notes') 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): # slot_id, file, volume, pitch, notes _iter = 0 self.slots = [] while _iter < 64: #def __init__(self, file, obj_id, o): p1 = self.SoundSlot(None, _iter, self) self.slots.append(p1) _iter += 1 #print('adding slot ', _iter) 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 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): 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)) def stop_playback(self): self.playing = False self.playhead = -1 self.playhead = 0 self.curPattern = self.song[0] pygame.time.set_timer(TIMER, 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 # For easy reading 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"] #self.black = self.theme["black"] #self.dark_blue = self.theme["dark_blue"] #self.green = self.theme["green"] #self.red = self.theme["red"] def save_song(self): base_dir = "/home/pi/zpc_ct/user/songs/" title = self.sconf['title'] fname = base_dir + self.sconf['title'] + '.sng' if os.path.exists(fname): os.utime(fname, None) print(fname, ' exists') else: print('make a damn file ', fname) 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/" print('before writing') print(self.mconf['theme']) 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") def load_song(self): #self.stop_playback() self.quit_mixer() self.start_mixer() base_dir = "/home/pi/zpc_ct/user/songs/" 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: #for s in self.sconf.as_list('sounds'): snd = self.sconf.as_list('sounds')[_iter] #print('-----') 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] _iter += 1 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 = 0 self.song = song self.songCycle = cycle(self.song) self.curPattern = next(self.songCycle) self.songStart = self.curPattern self.update_bpm() # if self.playing: # self.stop_playback() # self.start_playback() def update_display(self, x): # if self.display_thread.is_alive(): # pass # #self.disp.image(self.image, self.rotation) # else: # self.display_thread = threading.Thread(target=self.ud, args=([0])) # self.display_thread.start() # time.sleep(.001) 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() p = Prog() #while True: while not done: p.Execute() if pygame.event.get(pygame.USEREVENT + 1): #if p.playhead == 0: #print('pattern ', p.curPattern) p.playhead += 1 if p.playhead == 16: p.playhead = 0 p.curPattern = next(p.songCycle) #blink internal led if p.playhead % 8 == 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()