# Guinea synth # -*- coding: utf8 -*- # # built by Severák in awful year 2020 # # feel free to use in any way you like # because it's WTFPL licensed # but remember this is built on some GNU-licensed code, so respect it # # PIN numbers of rotary encoder - change these if needed: PIN_clk = 10 PIN_dt = 9 PIN_btn = 25 import sys import time from ctcsoundSession import CsoundSession def clip(value, lower, upper): return lower if value < lower else upper if value > upper else value # menu params class act_menu: params = [] current = 0 def __init__(self): self.params = [] def add(self, param): self.params.append(param) def enter(self): self.preview() def up(self): self.current = (self.current + 1) % len(self.params) self.preview() def down(self): self.current = (self.current - 1) % len(self.params) self.preview() def preview(self): self.params[self.current].preview() def push(self): self.params[self.current].enter() menu(self.params[self.current]) class act_select: label = '' params = [] current = 0 callback = None def __init__(self, label, params, callback): self.params=params self.callback=callback self.label = label def enter(self): cls() echo(self.label) echo('> {}'.format(self.params[self.current]), 1) def up(self): self.current = (self.current + 1) % len(self.params) self.enter() def down(self): self.current = (self.current - 1) % len(self.params) self.enter() def preview(self): cls() echo(self.label) echo('{}'.format(self.params[self.current]), 1) def push(self): self.callback(self.current, self.params[self.current]) class act_confirm: value = 0 prev = None question = "" callback = None def __init__(self, prev, question, callback): self.prev = prev self.question=question self.callback = callback def preview(self): cls() echo(self.question) echo("no", 1) def enter(self): cls() echo(self.question) echo("> yes" if self.value else "> no", 1) def up(self): self.value = 1 if (self.value==0) else 0 self.enter() def down(self): self.up() def push(self): if self.value==1: self.callback(); else: self.prev.preview() menu(self.prev) # CSD params class csd_param: value = 0 param_name = '' label = '' preset = 1 min = 0 max = 1 step = 1 csd = None prev = None def __init__(self, prev, csd, param_name, label, preset=1, min=0, max=1, per_round=20.0): self.prev = prev self.csd = csd self.param_name = param_name self.label = label self.preset = preset self.value = preset self.min = min self.max = max self.step = (max - min) / per_round csd.setControlChannel(param_name, preset) def preview(self): cls() echo(self.label) echo("{}".format(self.value), 1) def enter(self): cls() echo(self.label) echo("> {}".format(self.value), 1) def up(self): self.value = clip(self.value + self.step, self.min, self.max) self.csd.setControlChannel(self.param_name, self.value) self.enter() def down(self): self.value = clip(self.value - self.step, self.min, self.max) self.csd.setControlChannel(self.param_name, self.value) self.enter() def push(self): self.preview() menu(self.prev) class csd_select: value = 0 param_name = '' label = '' preset = 1 params = [] csd = None prev = None def __init__(self, prev, csd, param_name, label, preset=0, params=[]): self.prev = prev self.csd = csd self.param_name = param_name self.label = label self.preset = preset self.value = preset self.params = params csd.setControlChannel(param_name, preset+1) def preview(self): cls() echo(self.label) echo("{}".format(self.params[self.value]), 1) def enter(self): cls() echo(self.label) echo("> {}".format(self.params[self.value]), 1) def up(self): self.value = (self.value + 1) % len(self.params) self.csd.setControlChannel(self.param_name, self.value+1) self.enter() def down(self): self.value = (self.value + 1) % len(self.params) self.csd.setControlChannel(self.param_name, self.value+1) self.enter() def push(self): self.preview() menu(self.prev) class csd_checkbox: value = 0 param_name = '' label = '' preset = 1 csd = None prev = None def __init__(self, prev, csd, param_name, label, preset=0): self.prev = prev self.csd = csd self.param_name = param_name self.label = label self.preset = preset self.value = preset csd.setControlChannel(param_name, preset) def preview(self): cls() echo(self.label) echo("yes" if self.value else "no", 1) def enter(self): cls() echo(self.label) echo("> yes" if self.value else "> no", 1) def up(self): self.value = 1 if (self.value==0) else 0 self.csd.setControlChannel(self.param_name, self.value) self.enter() def down(self): self.up() def push(self): self.preview() menu(self.prev) # debug classes class odpalovac: value = 440 prev = None csd = None def __init__(self, prev, csd): self.prev = prev self.csd = csd def enter(self): self.preview() def preview(self): echo("Play note:") echo("{} Hz".format(self.value), 1) def up(self): self.value = clip(self.value +1, 20, 22000) self.show() def down(self): self.value = clip(self.value -1, 20, 22000) self.show() def push(self): self.csd.note([1,0,0.5,self.value]) self.prev.preview() menu(self.prev) class hladina: value = 0 prev = None name = "" def __init__(self, prev, name): self.prev = prev self.name = name def enter(self): self.preview() def preview(self): cls() echo(self.name) echo("{}".format(self.value), 1) def up(self): self.value += 1 self.show() def down(self): self.value -= 1 self.show() def push(self): self.prev.preview() menu(self.prev) # main _rotary = None print("Guinea synth") if "--tkinter" in sys.argv: print("(using Tkinter emulation instead of hardware)") import Tkinter as tk window = tk.Tk() window.geometry("200x150") rows = [None, None] rows[0] = tk.StringVar() tk.Label(text="", textvariable=rows[0]).pack() rows[1] = tk.StringVar() tk.Label(text="", textvariable=rows[1]).pack() tk.Button(text="UP", command=lambda: _rotary.up()).pack() tk.Button(text="ENTER", command=lambda: _rotary.push()).pack() tk.Button(text="DOWN", command=lambda: _rotary.down()).pack() def cls(): rows[0].set("") rows[1].set("") def echo(text, row=0): rows[row].set(text) def menu(callback): global _rotary if not _rotary: _rotary = callback window.mainloop() else: _rotary = callback else: print("(using hardware controls)") import lcddriver _display = lcddriver.lcd() def cls(): _display.lcd_clear() def echo(text, row=0): _display.lcd_display_string(text, row+1) from RPi import GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(PIN_clk, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(PIN_dt, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(PIN_btn, GPIO.IN, pull_up_down=GPIO.PUD_UP) clkLastState = GPIO.input(PIN_clk) btnLastState = GPIO.input(PIN_btn) def _pull_events(): global clkLastState global btnLastState while True: btnPushed = GPIO.input(PIN_btn) if ((not btnLastState) and btnPushed): _rotary.push() time.sleep(0.05) else: clkState = GPIO.input(PIN_clk) dtState = GPIO.input(PIN_dt) if clkState != clkLastState: if dtState != clkState: _rotary.up() else: _rotary.down(); clkLastState = clkState btnLastState = btnPushed def menu(callback): global _rotary if not _rotary: _rotary = callback _pull_events() else: _rotary = callback ###### def load_csd(idx, csd_path): global csd print(csd_path) menu_constructor = [] in_def = False with open(csd_path) as csdf: lines = csdf.readlines() for line in lines: if line.strip()=='': in_def = False if in_def: menu_constructor.append(line) if line.strip()=='': in_def = True cls() echo("reseting synth...") echo("please wait...", 1) csd.resetSession(csd_path) print("### params debug: ###") print(''.join(menu_constructor)) print("###") exec(''.join(menu_constructor)) cls() echo(csd_path) echo("READY", 1) try: import glob csd = CsoundSession() if sys.platform=='win32': csd.setOption('-+rtmidi=winmme') csd.setOption('-Ma') csd.setOption('-+rtaudio=portaudio') csd.setOption('-odac') csd.setOption('-b 4096') # TODO - fix latency on Windows. But how? # print(csd) # print(csd.audioDevList(True)) # print(csd.csdFileName()) csd_list = glob.glob("*.csd") system_menu = act_menu() system_menu.add(act_confirm(system_menu, 'Shutdown?', exit)) cls() echo("Guinea synth") echo("READY", 1) menu(act_select('Select synth:', csd_list, load_csd)) print("OK") finally: cls() csd.stopPerformance() del csd if not "--tkinter" in sys.argv: GPIO.cleanup()