guinea-synth/guinea-synth.py

466 lines
8.8 KiB
Python

# 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()=='</GuineaSynth>':
in_def = False
if in_def:
menu_constructor.append(line)
if line.strip()=='<GuineaSynth>':
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()