underbridge/underbridge.py

372 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Underbridge OP-Z multichannel exporter
# Copyright 2022 Thomas Herrmann Email: herrmann@raise-uav.com
import mido
import pyaudio
import wave
from tkinter import *
from tkinter import filedialog as fd
from tkinter import ttk
import time
import threading
import os
class Midirecorder:
def __init__(self):
self.window = Tk()
self.window.title('underbridge')
self.window.resizable(width=False, height=False) #565A5E
self.window.tk_setPalette(background='#565A5E', foreground='black',activeBackground='#283867', activeForeground='black' )
#device_list = []
self.op_device = []
self.audio_device = []
self.loop_time = 0
self.inport = 0
self.outport = 0
self.path = 0
self.folder = 0
self.pattern_nr = 0
self.j = 0
self.addsec = 0
self.projectpath = 0
self.cancel = 0
self.RATE = 0
self.mute_list =[0] * 14 #Midi mute selection of all 14 necessary channels
#modifier_dict = {"mod1": modifier1_va}
#GUI Main
self.buttonsize_x = 7
self.buttonsize_y = 2
self.mode_select = IntVar()
self.displaymsg = StringVar()
self.modifier1_value = IntVar()
self.modifier2_value = IntVar()
self.modifier3_value = IntVar()
self.modifier4_value = IntVar()
self.modifier5_value = IntVar()
self.modifier6_value = IntVar()
upperframe= LabelFrame(self.window, text= "Parameter",padx= 10, pady =2, fg = 'white')
upperframe.grid(row = 0, column = 0, padx =2, pady =2,)
lowerframe= Frame(self.window,padx= 10, pady =5)
lowerframe.grid(row = 2, column = 0, padx =2, pady =2)
modifiers = LabelFrame(self.window, text= "Exclude Modifiers",padx= 10, pady =2, fg = 'white')
modifiers.grid(row = 1, column = 0, padx =2, pady =2)
footer= Frame(self.window,padx= 15, pady =2)
footer. grid(row = 3, column = 0, padx =2, pady =2)
#Get_BPM = Button(upperframe, text="Get BPM",width = self.buttonsize_x, height = self.buttonsize_y, fg = 'lightgrey', command = getBPM)
Song = Radiobutton(lowerframe, text= 'Project', value = 2 , variable = self.mode_select, width = self.buttonsize_x, height = self.buttonsize_y , indicatoron = 0, bg= '#1b7d24' )
Pattern = Radiobutton(lowerframe, text= 'Pattern', value = 3 , variable = self.mode_select, width = self.buttonsize_x, height = self.buttonsize_y, indicatoron = 0,bg= '#1b7d24' )
Pattern.select()
self.bar_input = Scale(upperframe, from_ = 1, to = 9, orient = HORIZONTAL, label="Nr. Bars", sliderlength= 10, length= 75, fg = 'white')
self.patterns_input = Scale(upperframe, from_ = 1, to = 16, orient = HORIZONTAL, label="Patterns",sliderlength= 10, length= 75, fg = 'white')
self.patterns_input.set(value=16)
self.bpm_input = Entry(upperframe, width =10, text="BPM",bg= 'lightgrey', relief= FLAT)
self.bpm_input.insert(0, "BPM")
self.add_sec = Scale(upperframe, from_ = 0, to = 10, orient = HORIZONTAL, label="extra Sec", sliderlength= 10, length= 75, fg = 'white')
self.name_input = Entry(upperframe, width =10, text="Name",bg = 'lightgrey', relief= FLAT)
self.name_input.insert(0, "Name")
modifier1 = Checkbutton(modifiers, text="Send 1", variable=self.modifier1_value)
modifier1.grid(row = 0, column = 0, padx =5, pady =2)
modifier2 = Checkbutton(modifiers,text="Send 2", variable=self.modifier2_value)
modifier2.grid(row = 0, column = 1, padx =5, pady =2)
modifier3 = Checkbutton(modifiers,text="Tape",variable=self.modifier3_value )
modifier3.grid(row = 0, column = 2, padx =5, pady =2)
modifier4 = Checkbutton(modifiers,text="Master", variable= self.modifier4_value)
modifier4.grid(row = 0, column = 3, padx =5, pady =2)
modifier5 = Checkbutton(modifiers,text="Perform", variable= self.modifier5_value)
modifier5.grid(row = 0, column = 4, padx =5, pady =2)
modifier6 = Checkbutton(modifiers,text="Module", variable=self.modifier6_value)
modifier6.grid(row = 0, column = 5, padx =5, pady =2)
set_param = Button(lowerframe, text="Set Prmtr",width = self.buttonsize_x, height = self.buttonsize_y, fg = 'white',bg= '#0095FF', command = self.setParam)
set_path = Button(lowerframe, text="Directory",width = self.buttonsize_x, height = self.buttonsize_y,fg = 'white',bg= '#0095FF', command = self.setPath)
start_recording = Button(lowerframe, text="RECORD",width = self.buttonsize_x, height = self.buttonsize_y,fg = 'white', bg = '#FF2200', command = lambda:threading.Thread(target = self.sequenceMaster).start())
tutorial = Label(footer,text="Enter Parameter, then press set Param, choose directory and start recording", height = 2, bg ='grey',fg= 'white', relief = FLAT)
display = Label(lowerframe,textvariable= self.displaymsg,width = 60, height = self.buttonsize_y -1, bg ='lightgrey', relief = FLAT)
cancel = Button(lowerframe,text = "CANCEL" , width = self.buttonsize_x, height = self.buttonsize_y, bg ='#FFCC00', fg= 'white', command =self.cancelRec)
cancel.grid(row = 0, column = 6, padx =2, pady =2)
donate = Label(footer, text= "donate <3 @ https://link.raise-uav.com", height = 1)
donate.grid(row = 3, column = 4, padx =2, pady =10, columnspan=2)
Song.grid(row = 0, column = 1, padx =5, pady =2)
Pattern.grid(row = 0, column = 2, padx =5, pady =2)
self.name_input.grid(row = 0, column = 0, padx =5, pady =0)
self.bpm_input.grid(row = 0, column = 1, padx =5, pady =0)
self.bar_input.grid(row = 0, column = 3, padx =5, pady =2)
self.patterns_input.grid(row = 0, column = 4, padx =5, pady =2)
self.add_sec.grid(row = 0, column = 5, padx =5, pady =2)
set_param.grid(row = 0, column = 3, padx =5, pady =2)
set_path.grid(row = 0, column = 4, padx =5, pady =2)
start_recording.grid(row = 0, column = 5, padx =5, pady =2)
tutorial.grid(row = 1, column = 0, padx =5, pady =5, columnspan=5)
display.grid(row = 1, column = 0, padx =2, pady =10, columnspan= 7)
self.window.mainloop()
def getMIDIDevice(self):
#global device_list
#global op_device
device_list = mido.get_output_names()
print (device_list)
try:
self.op_device = list(filter(lambda x: 'OP-Z' in x, device_list))
self.op_device = self.op_device[0]
#print (self.op_device)
self.displaymsg.set("OP-Z found")
except:
self.displaymsg.set("Can´t find OP-Z : MIDI Error.")
def getAudioDevice(self):
#global audio_device
#global RATE
p = pyaudio.PyAudio()
try:
info = p.get_host_api_info_by_index(0)
numdevices = info.get('deviceCount')
for i in range(0, numdevices):
if (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
print("Input Device id ", i, " - ", p.get_device_info_by_host_api_device_index(0, i).get('name'))
for i in range(0, numdevices):
#audio_device = p.get_device_info_by_host_api_device_index(0, i).get('name')
if "OP-Z" in p.get_device_info_by_host_api_device_index(0, i).get('name') and (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
self.audio_device = i
#audio_device = 4
print ("Detected OP-Z audio at Index:",self.audio_device, p.get_device_info_by_host_api_device_index(0, self.audio_device).get('name'))
except:
self.displaymsg.set("OP-Z Audio Device not found.")
#devinfo = p.get_device_info_by_index(self.audio_device)
try:
devinfo = p.get_device_info_by_index(self.audio_device)
test = p.is_format_supported(48000, input_device=devinfo['index'], input_channels=devinfo['maxInputChannels'],input_format=pyaudio.paInt16)
self.RATE = 48000
print("48kHz")
except:
self.RATE = 44100
print("44100kHz compatibility mode")
def getBPM(self):
inport= mido.open_input(self.op_device)
msg = inport.poll(self)
#print(msg)
def setLoop(self):
try:
bpm = self.bpm_input.get()
bar = self.bar_input.get()
addsec = self.add_sec.get()
self.loop_time = (240 / int(bpm) * int(bar)) + int(addsec)
print("Loop time set!", self.loop_time)
self.displaymsg.set("BPM Set!")
except:
self.displaymsg.set("Please enter accurate BPM.")
#return self.loop_time
def setParam(self):
self.setLoop()
#mode = mode_select.get()
#if mode == 2:
# projnr = project_input.get()
# setProject(projnr)
def openMidi(self):
#global outport
#global op_device
self.outport= mido.open_output(self.op_device)
#displaymsg.set("OP-Z MIDI not connected :(")
#print(self.outport)
def setProject(self,projnr):
msg= mido.Message('program_change',song= self.projnr, program = 1)
self.outport.send(msg)
def muteAll(self):
checkbutton_name = 0
#print(self.mute_list)
for j in range (0,8):
self.mute_list[j] = 1
for i in range (1,7):
checkbutton_name = 'self.modifier{}_value'.format(i) #checkbutton 1- 6
self.mute_list[i+7] = eval(checkbutton_name).get() #9th position in mute list
for k in range (0,14):
msg = mido.Message('control_change',control= 53, channel= k, value= self.mute_list[k])
self.outport.send(msg)
#print("Muted Channels",self.mute_list)
def setSolo(self,chn):
msg = mido.Message('control_change',control= 53, channel= chn, value=0)
self.outport.send(msg)
def start_MIDI(self):
msg = mido.Message('start')
self.outport.send(msg)
self.displaymsg.set("Playback started")
#print("midi")
def stop_MIDI(self):
msg = mido.Message('stop')
self.outport.send(msg)
self.displaymsg.set("Playback stopped")
def unmuteAll(self):
for i in range (0,15):
msg = mido.Message('control_change',control= 53, channel= i, value=0)
self.outport.send(msg)
def nextPattern(self):
msg = mido.Message('control_change', control = 103, value = 16)
self.outport.send(msg)
self.displaymsg.set("Next Pattern")
def nextSong(self):
pass
def closeMidi(self):
self.outport.close()
self.displaymsg.set("MIDI closed")
def setPath(self):
#global path
folder = self.name_input.get()
path = fd.askdirectory()
self.displaymsg.set("Directory set!")
self.makeDir(path,folder)
def makeDir(self,path,folder):
#global folder
#global projectpath
#folder = name_input.get()
self.projectpath = path + '/' + folder
try:
os.mkdir(self.projectpath)
except:
self.displaymsg.set("Directory Error. Please enter different Name.")
def makeDirNr(self, pattern_nr):
#global projectpath
#Pfad wird addiert deswegen zusätzliche verzeichnisse
#projectpath = projectpath + '/' + str(pattern_nr)
try:
os.mkdir(self.projectpath + '/' + str(pattern_nr))
except:
self.displaymsg.set("Directory Error")
#print(projectpath)
def start_Rec(self):
#print("record")
self.displaymsg.set("Recording...")
CHUNK = 128
FORMAT = pyaudio.paInt16
CHANNELS = 2
RECORD_SECONDS= self.loop_time
#print("record")
WAVE_OUTPUT_FILENAME = self.name_input.get() + "_" + "track" + str(self.j+1) + ".wav"
#print(WAVE_OUTPUT_FILENAME)
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=self.RATE,
input=True,
input_device_index= self.audio_device,
frames_per_buffer=CHUNK
)
#print("* recording")
frames = []
self.start_MIDI()
for i in range(0, int(self.RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
#print("Done recording")
stream.stop_stream()
stream.close()
p.terminate()
if self.mode_select.get() == 2:
wf = wave.open(self.projectpath + '/' + str(self.pattern_nr) + '/' + WAVE_OUTPUT_FILENAME, 'wb')
else:
wf = wave.open(self.projectpath + '/' + WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(self.RATE)
wf.writeframes(b''.join(frames))
wf.close()
self.j = self.j + 1
if self.j == 8:
self.j= 0
self.displaymsg.set("End of Recording")
def sequenceMaster(self):
self.cancel = 0
self.getMIDIDevice()
time.sleep(1)
self.getAudioDevice()
self.displaymsg.set("Sequence started")
try:
self.openMidi()
if self.mode_select.get() == 2:
self.makeDirNr(self.pattern_nr)
for i in range (0,8):
pattern_limit = self.patterns_input.get()
if self.cancel == 1 or self.pattern_nr == pattern_limit:
break
#print("sequence started",i)
self.muteAll()
time.sleep(0.1)
self.setSolo(i)
#starting Midi during wave record for timing
self.start_Rec()
self.stop_MIDI()
time.sleep(1)
self.unmuteAll()
time.sleep(1)
mode = self.mode_select.get()
if i == 7 and mode == 2:
#print(mode_select)
time.sleep(5)
self.nextPattern()
self.pattern_nr += 1
if self.pattern_nr == 15 :
self.pattern_nr = 0
self.sequenceMaster()
except:
self.displaymsg.set("OP-Z Sequence error try restarting the OP-Z or press CANCEL Button")
def cancelRec(self):
self.j = 0
self.cancel = 1
self.closeMidi()
underbridge = Midirecorder()